├── .gitignore
├── .swift-version
├── CRRefresh.png
├── CRRefresh.podspec
├── CRRefresh.xcodeproj
├── project.pbxproj
├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
└── xcshareddata
│ └── xcschemes
│ └── CRRefresh.xcscheme
├── CRRefresh
├── CRRefresh
│ ├── Animators
│ │ ├── FastAnimator
│ │ │ ├── FastAnimator.swift
│ │ │ ├── FastArrowLayer.swift
│ │ │ ├── FastCheckLayer.swift
│ │ │ ├── FastCircleLayer.swift
│ │ │ └── FastLayer.swift
│ │ ├── NormalAnimator
│ │ │ ├── NormalFooter.bundle
│ │ │ │ ├── en.lproj
│ │ │ │ │ └── Localizable.strings
│ │ │ │ └── zh.lproj
│ │ │ │ │ └── Localizable.strings
│ │ │ ├── NormalFooterAnimator.swift
│ │ │ ├── NormalHeader.bundle
│ │ │ │ ├── en.lproj
│ │ │ │ │ └── Localizable.strings
│ │ │ │ ├── images
│ │ │ │ │ ├── refresh_arrow@2x.png
│ │ │ │ │ └── refresh_arrow@3x.png
│ │ │ │ └── zh.lproj
│ │ │ │ │ └── Localizable.strings
│ │ │ └── NormalHeaderAnimator.swift
│ │ ├── RamotionAnimator
│ │ │ ├── RamotionAnimator.swift
│ │ │ ├── RamotionBallLayer.swift
│ │ │ ├── RamotionBounceLayer.swift
│ │ │ └── RamotionWaveLayer.swift
│ │ └── SlackLoadingAnimator
│ │ │ ├── SlackLoadingAnimator.swift
│ │ │ └── WCLLoadingView.swift
│ ├── CRRefreshAnimator.swift
│ ├── CRRefreshBundle.swift
│ ├── CRRefreshComponent.swift
│ ├── CRRefreshExtension.swift
│ ├── CRRefreshFooterView.swift
│ ├── CRRefreshHeaderView.swift
│ ├── CRRefreshProtocol.swift
│ └── UIColorExtension.swift
└── Info.plist
├── CRRefresh1.gif
├── CRRefresh2.gif
├── CRRefresh3.gif
├── CRRefresh4.gif
├── Demo
├── AppDelegate.swift
├── Assets.xcassets
│ ├── AppIcon.appiconset
│ │ ├── Contents.json
│ │ ├── Icon-App-20x20@2x.png
│ │ ├── Icon-App-20x20@3x.png
│ │ ├── Icon-App-29x29@1x.png
│ │ ├── Icon-App-29x29@2x.png
│ │ ├── Icon-App-29x29@3x.png
│ │ ├── Icon-App-40x40@2x.png
│ │ ├── Icon-App-40x40@3x.png
│ │ ├── Icon-App-57x57@1x.png
│ │ ├── Icon-App-57x57@2x.png
│ │ ├── Icon-App-60x60@2x.png
│ │ ├── Icon-App-60x60@3x.png
│ │ ├── Icon-ios-App-1024x1024@1x.png
│ │ ├── Icon-ipad-App-20x20@1x.png
│ │ ├── Icon-ipad-App-20x20@2x.png
│ │ ├── Icon-ipad-App-29x29@1x.png
│ │ ├── Icon-ipad-App-29x29@2x.png
│ │ ├── Icon-ipad-App-40x40@1x.png
│ │ ├── Icon-ipad-App-40x40@2x.png
│ │ ├── Icon-ipad-App-72x72@1x.png
│ │ ├── Icon-ipad-App-72x72@2x.png
│ │ ├── Icon-ipad-App-76x76@1x.png
│ │ ├── Icon-ipad-App-76x76@2x.png
│ │ ├── Icon-ipad-App-83.5x83.5@2x.png
│ │ ├── Icon-ipad-Small-50x50@1x.png
│ │ └── Icon-ipad-Small-50x50@2x.png
│ ├── Image_1.imageset
│ │ ├── 3(1).png
│ │ └── Contents.json
│ ├── Image_2.imageset
│ │ ├── 2(1).png
│ │ └── Contents.json
│ ├── Image_3.imageset
│ │ ├── 1.png
│ │ └── Contents.json
│ └── nav_back.imageset
│ │ ├── Contents.json
│ │ ├── icon-返回@2x.png
│ │ └── icon-返回@3x.png
├── Base.lproj
│ └── LaunchScreen.storyboard
├── BaseViewController.swift
├── Cells
│ ├── NormalCell.swift
│ ├── NormalCell.xib
│ ├── RefreshCell.swift
│ └── RefreshCell.xib
├── Info.plist
├── NavigationController.swift
├── RefreshController.swift
└── ViewController.swift
├── LICENSE
├── README.md
└── README_CN.md
/.gitignore:
--------------------------------------------------------------------------------
1 | ld/
2 | *.pbxuser
3 | !default.pbxuser
4 | *.mode1v3
5 | !default.mode1v3
6 | *.mode2v3
7 | !default.mode2v3
8 | *.perspectivev3
9 | !default.perspectivev3
10 | xcuserdata
11 | *.xccheckout
12 | *.moved-aside
13 | DerivedData
14 | *.hmap
15 | *.ipa
16 | *.xcuserstate
17 | *.xcuserdatad
18 |
19 | Podfile.lock
20 | */Pods
21 |
--------------------------------------------------------------------------------
/.swift-version:
--------------------------------------------------------------------------------
1 | 4.2
--------------------------------------------------------------------------------
/CRRefresh.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CRAnimation/CRRefresh/336441963819645dc7427352664f114f5a4cc864/CRRefresh.png
--------------------------------------------------------------------------------
/CRRefresh.podspec:
--------------------------------------------------------------------------------
1 | #
2 | # Be sure to run `pod spec lint CRRefresh.podspec' to ensure this is a
3 | # valid spec and to remove all comments including this before submitting the spec.
4 | #
5 | # To learn more about Podspec attributes see http://docs.cocoapods.org/specification.html
6 | # To see working Podspecs in the CocoaPods repo see https://github.com/CocoaPods/Specs/
7 | #
8 |
9 | Pod::Spec.new do |s|
10 |
11 | s.name = "CRRefresh"
12 | s.version = "1.1.3"
13 | s.summary = "An easy way to use pull-to-refresh"
14 | s.homepage = "https://github.com/CRAnimation/CRRefresh"
15 | s.license = 'MIT'
16 | s.author = { "W_C__L" => "wangchonglei93@icloud.com" }
17 | s.platform = :ios, "8.0"
18 | s.swift_version = '4.2'
19 | s.source = { :git => "https://github.com/CRAnimation/CRRefresh.git", :tag => s.version.to_s }
20 | s.source_files = ['CRRefresh/CRRefresh/*.{swift}','CRRefresh/CRRefresh/Animators/**/*.{swift}']
21 | s.resources = 'CRRefresh/CRRefresh/Animators/**/*.{bundle}'
22 | s.frameworks = "UIKit"
23 | s.requires_arc = true
24 |
25 | end
26 |
--------------------------------------------------------------------------------
/CRRefresh.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/CRRefresh.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/CRRefresh.xcodeproj/xcshareddata/xcschemes/CRRefresh.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
32 |
33 |
34 |
35 |
45 |
46 |
52 |
53 |
54 |
55 |
56 |
57 |
63 |
64 |
70 |
71 |
72 |
73 |
75 |
76 |
79 |
80 |
81 |
--------------------------------------------------------------------------------
/CRRefresh/CRRefresh/Animators/FastAnimator/FastAnimator.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FastAnimator.swift
3 | // FastAnimator
4 | //
5 | // **************************************************
6 | // * _____ *
7 | // * __ _ __ ___ \ / *
8 | // * \ \/ \/ / / __\ / / *
9 | // * \ _ / | (__ / / *
10 | // * \/ \/ \___/ / /__ *
11 | // * /_____/ *
12 | // * *
13 | // **************************************************
14 | // Github :https://github.com/imwcl
15 | // HomePage:http://imwcl.com
16 | // CSDN :http://blog.csdn.net/wang631106979
17 | //
18 | // Created by 王崇磊 on 16/9/14.
19 | // Copyright © 2016年 王崇磊. All rights reserved.
20 | //
21 | // @class FastAnimator
22 | // @abstract FastAnimator
23 | // @discussion FastAnimator
24 | //
25 |
26 | import UIKit
27 |
28 | open class FastAnimator: UIView, CRRefreshProtocol {
29 |
30 | open var view: UIView { return self }
31 |
32 | open var insets: UIEdgeInsets = .zero
33 |
34 | open var trigger: CGFloat = 55.0
35 |
36 | open var execute: CGFloat = 55.0
37 |
38 | open var endDelay: CGFloat = 1.5
39 |
40 | open var hold: CGFloat = 55.0
41 |
42 | private(set) var color: UIColor = .init(rgb: (214, 214, 214))
43 |
44 | private(set) var arrowColor: UIColor = .init(rgb: (165, 165, 165))
45 |
46 | private(set) var lineWidth: CGFloat = 1
47 |
48 | private(set) var fastLayer: FastLayer?
49 |
50 |
51 | //MARK: CRRefreshProtocol
52 | /// 开始刷新
53 | open func refreshBegin(view: CRRefreshComponent) {
54 | fastLayer?.arrow?.startAnimation().animationEnd = { [weak self] in
55 | self?.fastLayer?.circle?.startAnimation()
56 | }
57 | }
58 |
59 | /// 结束刷新
60 | open func refreshEnd(view: CRRefreshComponent, finish: Bool) {
61 | if finish {
62 | fastLayer?.arrow?.endAnimation()
63 | fastLayer?.circle?.endAnimation(finish: finish)
64 | fastLayer?.arrow?.setAffineTransform(CGAffineTransform.identity)
65 | }
66 | }
67 |
68 | open func refreshWillEnd(view: CRRefreshComponent) {
69 | fastLayer?.circle?.endAnimation(finish: false)
70 | }
71 |
72 | /// 刷新进度的变化
73 | open func refresh(view: CRRefreshComponent, progressDidChange progress: CGFloat) {
74 | if progress >= 1 {
75 | let transform = CGAffineTransform.identity.rotated(by: CGFloat(Double.pi))
76 | fastLayer?.arrow?.setAffineTransform(transform)
77 | } else {
78 | let transform = CGAffineTransform.identity.rotated(by: CGFloat(2 * Double.pi))
79 | fastLayer?.arrow?.setAffineTransform(transform)
80 | }
81 | }
82 |
83 | /// 刷新状态的变化
84 | open func refresh(view: CRRefreshComponent, stateDidChange state: CRRefreshState) {
85 |
86 | }
87 |
88 | //MARK: Override
89 | override init(frame: CGRect) {
90 | super.init(frame: frame)
91 | backgroundColor = UIColor.clear
92 | }
93 |
94 | open override func layoutSubviews() {
95 | super.layoutSubviews()
96 | if fastLayer == nil {
97 | let width = frame.width
98 | let height = frame.height
99 | fastLayer = FastLayer(frame: .init(x: width/2 - 14,
100 | y: height/2 - 14,
101 | width: 28, height: 28),
102 | color: color,
103 | arrowColor: arrowColor,
104 | lineWidth: lineWidth)
105 | layer.addSublayer(fastLayer!)
106 | }
107 | }
108 |
109 | //MARK: Initial Methods
110 | public init(frame: CGRect,
111 | color: UIColor = .init(rgb: (214, 214, 214)),
112 | arrowColor: UIColor = .init(rgb: (165, 165, 165)),
113 | lineWidth: CGFloat = 1) {
114 | self.color = color
115 | self.arrowColor = arrowColor
116 | self.lineWidth = lineWidth
117 | super.init(frame: frame)
118 | backgroundColor = UIColor.clear
119 | }
120 |
121 | required public init?(coder aDecoder: NSCoder) {
122 | fatalError("init(coder:) has not been implemented")
123 | }
124 |
125 | }
126 |
--------------------------------------------------------------------------------
/CRRefresh/CRRefresh/Animators/FastAnimator/FastArrowLayer.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FastArrowLayer.swift
3 | // FastAnimator
4 | //
5 | // **************************************************
6 | // * _____ *
7 | // * __ _ __ ___ \ / *
8 | // * \ \/ \/ / / __\ / / *
9 | // * \ _ / | (__ / / *
10 | // * \/ \/ \___/ / /__ *
11 | // * /_____/ *
12 | // * *
13 | // **************************************************
14 | // Github :https://github.com/imwcl
15 | // HomePage:https://imwcl.com
16 | // CSDN :http://blog.csdn.net/wang631106979
17 | //
18 | // Created by 王崇磊 on 16/9/14.
19 | // Copyright © 2016年 王崇磊. All rights reserved.
20 | //
21 | // @class FastArrowLayer
22 | // @abstract 箭头的layer
23 | // @discussion 箭头的layer
24 | //
25 |
26 | import UIKit
27 |
28 | class FastArrowLayer: CALayer,
29 | CAAnimationDelegate {
30 |
31 | var color: UIColor = UIColor.init(rgb: (165, 165, 165))
32 |
33 | var lineWidth: CGFloat = 1
34 |
35 | private var lineLayer: CAShapeLayer?
36 |
37 | private var arrowLayer: CAShapeLayer?
38 |
39 | private let animationDuration: Double = 0.2
40 |
41 | var animationEnd: (()->Void)?
42 |
43 | //MARK: Initial Methods
44 | init(frame: CGRect,
45 | color: UIColor = .init(rgb: (165, 165, 165)),
46 | lineWidth: CGFloat = 1) {
47 | self.color = color
48 | self.lineWidth = lineWidth
49 | super.init()
50 | self.frame = frame
51 | backgroundColor = UIColor.clear.cgColor
52 | initLineLayer()
53 | initArrowLayer()
54 | }
55 |
56 | required init?(coder aDecoder: NSCoder) {
57 | fatalError("init(coder:) has not been implemented")
58 | }
59 |
60 | //MARK: Privater Methods
61 | private func initLineLayer() {
62 | let width = frame.size.width
63 | let height = frame.size.height
64 | let path = UIBezierPath()
65 | path.move(to: .init(x: width/2, y: 0))
66 | path.addLine(to: .init(x: width/2, y: height/2 + height/3))
67 | lineLayer = CAShapeLayer()
68 | lineLayer?.lineWidth = lineWidth*2
69 | lineLayer?.strokeColor = color.cgColor
70 | lineLayer?.fillColor = UIColor.clear.cgColor
71 | lineLayer?.lineCap = CAShapeLayerLineCap.round
72 | lineLayer?.path = path.cgPath
73 | lineLayer?.strokeStart = 0.5
74 | addSublayer(lineLayer!)
75 | }
76 |
77 | private func initArrowLayer() {
78 | let width = frame.size.width
79 | let height = frame.size.height
80 | let path = UIBezierPath()
81 | path.move(to: .init(x: width/2 - height/6, y: height/2 + height/6))
82 | path.addLine(to: .init(x: width/2, y: height/2 + height/3))
83 | path.addLine(to: .init(x: width/2 + height/6, y: height/2 + height/6))
84 | arrowLayer = CAShapeLayer()
85 | arrowLayer?.lineWidth = lineWidth*2
86 | arrowLayer?.strokeColor = color.cgColor
87 | arrowLayer?.lineCap = CAShapeLayerLineCap.round
88 | arrowLayer?.lineJoin = CAShapeLayerLineJoin.round
89 | arrowLayer?.fillColor = UIColor.clear.cgColor
90 | arrowLayer?.path = path.cgPath
91 | addSublayer(arrowLayer!)
92 | }
93 |
94 | //MARK: public Methods
95 | @discardableResult
96 | func startAnimation() -> Self {
97 | let start = CABasicAnimation(keyPath: "strokeStart")
98 | start.duration = animationDuration
99 | start.fromValue = 0
100 | start.toValue = 0.5
101 | start.isRemovedOnCompletion = false
102 | start.fillMode = CAMediaTimingFillMode.forwards
103 | start.delegate = self
104 | start.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)
105 |
106 | let end = CABasicAnimation(keyPath: "strokeEnd")
107 | end.duration = animationDuration
108 | end.fromValue = 1
109 | end.toValue = 0.5
110 | end.isRemovedOnCompletion = false
111 | end.fillMode = CAMediaTimingFillMode.forwards
112 | end.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)
113 |
114 | arrowLayer?.add(start, forKey: "strokeStart")
115 | arrowLayer?.add(end, forKey: "strokeEnd")
116 |
117 | return self
118 | }
119 |
120 | func endAnimation() {
121 | arrowLayer?.isHidden = false
122 | lineLayer?.isHidden = false
123 | arrowLayer?.removeAllAnimations()
124 | lineLayer?.removeAllAnimations()
125 | }
126 |
127 | private func addLineAnimation() {
128 | let start = CABasicAnimation(keyPath: "strokeStart")
129 | start.fromValue = 0.5
130 | start.toValue = 0
131 | start.isRemovedOnCompletion = false
132 | start.fillMode = CAMediaTimingFillMode.forwards
133 | start.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)
134 | start.duration = animationDuration/2
135 | lineLayer?.add(start, forKey: "strokeStart")
136 |
137 | let end = CABasicAnimation(keyPath: "strokeEnd")
138 | end.beginTime = CACurrentMediaTime() + animationDuration/3
139 | end.duration = animationDuration/2
140 | end.fromValue = 1
141 | end.toValue = 0.03
142 | end.isRemovedOnCompletion = false
143 | end.fillMode = CAMediaTimingFillMode.forwards
144 | end.delegate = self
145 | end.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)
146 | lineLayer?.add(end, forKey: "strokeEnd")
147 | }
148 |
149 | func animationDidStop(_ anim: CAAnimation, finished flag: Bool) {
150 | if let anim = anim as? CABasicAnimation {
151 | if anim.keyPath == "strokeStart" {
152 | arrowLayer?.isHidden = true
153 | addLineAnimation()
154 | }else {
155 | lineLayer?.isHidden = true
156 | animationEnd?()
157 | }
158 | }
159 | }
160 |
161 | override init(layer: Any) {
162 | super.init(layer: layer)
163 | }
164 | }
165 |
--------------------------------------------------------------------------------
/CRRefresh/CRRefresh/Animators/FastAnimator/FastCheckLayer.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FastCheckLayer.swift
3 | // CRRefresh
4 | //
5 | // **************************************************
6 | // * _____ *
7 | // * __ _ __ ___ \ / *
8 | // * \ \/ \/ / / __\ / / *
9 | // * \ _ / | (__ / / *
10 | // * \/ \/ \___/ / /__ *
11 | // * /_____/ *
12 | // * *
13 | // **************************************************
14 | // Github :https://github.com/imwcl
15 | // HomePage:https://imwcl.com
16 | // CSDN :http://blog.csdn.net/wang631106979
17 | //
18 | // Created by 王崇磊 on 16/9/14.
19 | // Copyright © 2016年 王崇磊. All rights reserved.
20 | //
21 | // @class FastCheckLayer
22 | // @abstract FastCheckLayer
23 | // @discussion FastCheckLayer
24 | //
25 |
26 | import UIKit
27 |
28 | class FastCheckLayer: CALayer {
29 |
30 | private(set) var check: CAShapeLayer?
31 |
32 | let color: UIColor
33 |
34 | let lineWidth: CGFloat
35 |
36 | //MARK: Public Methods
37 | func startAnimation() {
38 | let start = CAKeyframeAnimation(keyPath: "strokeStart")
39 | start.values = [0, 0.4, 0.3]
40 | start.isRemovedOnCompletion = false
41 | start.fillMode = CAMediaTimingFillMode.forwards
42 | start.duration = 0.5
43 | start.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)
44 |
45 | let end = CAKeyframeAnimation(keyPath: "strokeEnd")
46 | end.values = [0, 1, 0.9]
47 |
48 | end.isRemovedOnCompletion = false
49 | end.fillMode = CAMediaTimingFillMode.forwards
50 | end.duration = 0.8
51 | end.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)
52 |
53 | check?.add(start, forKey: "start")
54 | check?.add(end, forKey: "end")
55 | }
56 |
57 | func endAnimation() {
58 | check?.removeAllAnimations()
59 | }
60 |
61 | //MARK: Initial Methods
62 | init(frame: CGRect, color: UIColor = .init(rgb: (165, 165, 165)), lineWidth: CGFloat = 1) {
63 | self.color = color
64 | self.lineWidth = lineWidth*2
65 | super.init()
66 | self.frame = frame
67 | backgroundColor = UIColor.clear.cgColor
68 | drawCheck()
69 | }
70 |
71 | required init?(coder aDecoder: NSCoder) {
72 | fatalError("init(coder:) has not been implemented")
73 | }
74 |
75 | //MARK: Privater Methods
76 | private func drawCheck() {
77 | let width = Double(frame.size.width)
78 | check = CAShapeLayer()
79 | check?.lineCap = CAShapeLayerLineCap.round
80 | check?.lineJoin = CAShapeLayerLineJoin.round
81 | check?.lineWidth = lineWidth
82 | check?.fillColor = UIColor.clear.cgColor
83 | check?.strokeColor = color.cgColor
84 | check?.strokeStart = 0
85 | check?.strokeEnd = 0
86 | let path = UIBezierPath()
87 | let a = sin(0.4) * (width/2)
88 | let b = cos(0.4) * (width/2)
89 | path.move(to: CGPoint.init(x: width/2 - b, y: width/2 - a))
90 | path.addLine(to: CGPoint.init(x: width/2 - width/20 , y: width/2 + width/8))
91 | path.addLine(to: CGPoint.init(x: width - width/5, y: width/2 - a))
92 | check?.path = path.cgPath
93 | addSublayer(check!)
94 | }
95 |
96 | }
97 |
--------------------------------------------------------------------------------
/CRRefresh/CRRefresh/Animators/FastAnimator/FastCircleLayer.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FastCircleLayer.swift
3 | // FastAnimator
4 | //
5 | // **************************************************
6 | // * _____ *
7 | // * __ _ __ ___ \ / *
8 | // * \ \/ \/ / / __\ / / *
9 | // * \ _ / | (__ / / *
10 | // * \/ \/ \___/ / /__ *
11 | // * /_____/ *
12 | // * *
13 | // **************************************************
14 | // Github :https://github.com/imwcl
15 | // HomePage:https://imwcl.com
16 | // CSDN :http://blog.csdn.net/wang631106979
17 | //
18 | // Created by 王崇磊 on 16/9/14.
19 | // Copyright © 2016年 王崇磊. All rights reserved.
20 | //
21 | // @class FastCircleLayer
22 | // @abstract 外层圆环的layer
23 | // @discussion 外层圆环的layer
24 | //
25 |
26 | import UIKit
27 |
28 | class FastCircleLayer: CALayer {
29 |
30 | let color: UIColor
31 |
32 | let pointColor: UIColor
33 |
34 | let lineWidth: CGFloat
35 |
36 | let circle = CAShapeLayer()
37 |
38 | let point = CAShapeLayer()
39 |
40 | private let pointBack = CALayer()
41 |
42 | private var rotated: CGFloat = 0
43 |
44 | private var rotatedSpeed: CGFloat = 0
45 |
46 | private var speedInterval: CGFloat = 0
47 |
48 | private var stop: Bool = false
49 |
50 | private(set) var check: FastCheckLayer?
51 |
52 | var codeTimer: DispatchSourceTimer?
53 |
54 | //MARK: Initial Methods
55 | init(frame: CGRect,
56 | color: UIColor = .init(rgb: (214, 214, 214)),
57 | pointColor: UIColor = .init(rgb: (165, 165, 165)),
58 | lineWidth: CGFloat = 1) {
59 | self.color = color
60 | self.lineWidth = lineWidth
61 | self.pointColor = pointColor
62 | pointBack.frame = CGRect(x: 0, y: 0, width: frame.size.width, height: frame.size.height)
63 | super.init()
64 | self.frame = frame
65 | backgroundColor = UIColor.clear.cgColor
66 | pointBack.backgroundColor = UIColor.clear.cgColor
67 | drawCircle()
68 | addSublayer(pointBack)
69 | drawPoint()
70 | addCheckLayer()
71 | }
72 |
73 | required init?(coder aDecoder: NSCoder) {
74 | fatalError("init(coder:) has not been implemented")
75 | }
76 |
77 | //MARK: Public Methods
78 | func startAnimation() {
79 | circle.isHidden = false
80 | point.isHidden = false
81 |
82 | codeTimer = DispatchSource.makeTimerSource(queue: DispatchQueue.global())
83 | codeTimer?.schedule(deadline: .now(), repeating: .milliseconds(42))
84 | codeTimer?.setEventHandler(handler: { [weak self] in
85 | guard let `self` = self else {
86 | return
87 | }
88 | self.rotated = self.rotated - self.rotatedSpeed
89 | if self.stop {
90 | let count = Int(self.rotated / CGFloat(Double.pi * 2))
91 | if (CGFloat(Double.pi * 2 * Double(count)) - self.rotated) >= 1.1 {
92 | var transform = CGAffineTransform.identity
93 | transform = transform.rotated(by: -1.1)
94 | DispatchQueue.main.async {
95 | self.pointBack.setAffineTransform(transform)
96 | self.point.isHidden = true
97 | self.check?.startAnimation()
98 | }
99 | self.codeTimer?.cancel()
100 | return
101 | }
102 | }
103 | if self.rotatedSpeed < 0.65 {
104 | if self.speedInterval < 0.02 {
105 | self.speedInterval = self.speedInterval + 0.001
106 | }
107 | self.rotatedSpeed = self.rotatedSpeed + self.speedInterval
108 | }
109 | var transform = CGAffineTransform.identity
110 | transform = transform.rotated(by: self.rotated)
111 | DispatchQueue.main.async {
112 | self.pointBack.setAffineTransform(transform)
113 | }
114 | })
115 | codeTimer?.resume()
116 |
117 | addPointAnimation()
118 | }
119 |
120 | func endAnimation(finish: Bool) {
121 | if finish {
122 | stop = false
123 | rotated = 0
124 | rotatedSpeed = 0
125 | speedInterval = 0
126 | pointBack.setAffineTransform(CGAffineTransform.identity)
127 | circle.isHidden = true
128 | point.isHidden = true
129 | codeTimer?.cancel()
130 | check?.endAnimation()
131 | }else {
132 | DispatchQueue.global().async {
133 | self.stop = true
134 | }
135 | }
136 | }
137 |
138 | //MARK: Privater Methods
139 | private func drawCircle() {
140 | let width = frame.size.width
141 | let height = frame.size.height
142 | let path = UIBezierPath()
143 | path.addArc(withCenter: .init(x: width/2,
144 | y: height/2),
145 | radius: height/2,
146 | startAngle: 0,
147 | endAngle: CGFloat(Double.pi * 2.0),
148 | clockwise: false)
149 | circle.lineWidth = lineWidth
150 | circle.strokeColor = color.cgColor
151 | circle.fillColor = UIColor.clear.cgColor
152 | circle.path = path.cgPath
153 | addSublayer(circle)
154 | circle.isHidden = true
155 | }
156 |
157 | private func drawPoint() {
158 | let width = frame.size.width
159 | let path = UIBezierPath()
160 | path.addArc(withCenter: .init(x: width/2, y: width/2),
161 | radius: width/2,
162 | startAngle: CGFloat(Double.pi * 0.5),
163 | endAngle: CGFloat((Double.pi * 0.5) - 0.1),
164 | clockwise: false)
165 | point.lineCap = CAShapeLayerLineCap.round
166 | point.lineWidth = lineWidth*2
167 | point.fillColor = UIColor.clear.cgColor
168 | point.strokeColor = pointColor.cgColor
169 | point.path = path.cgPath
170 | pointBack.addSublayer(point)
171 | point.isHidden = true
172 | }
173 |
174 | private func addPointAnimation() {
175 | let width = frame.size.width
176 | let path = CABasicAnimation(keyPath: "path")
177 | path.beginTime = CACurrentMediaTime() + 1
178 | path.fromValue = point.path
179 | let toPath = UIBezierPath()
180 | toPath.addArc(withCenter: .init(x: width/2, y: width/2),
181 | radius: width/2,
182 | startAngle: CGFloat(Double.pi * 0.5),
183 | endAngle: CGFloat((Double.pi * 0.5) - 0.3),
184 | clockwise: false)
185 | path.toValue = toPath.cgPath
186 | path.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeOut)
187 | path.duration = 2
188 | path.isRemovedOnCompletion = false
189 | path.fillMode = CAMediaTimingFillMode.forwards
190 | point.add(path, forKey: "path")
191 | }
192 |
193 | private func addCheckLayer() {
194 | check = FastCheckLayer(frame: CGRect(x: 0,
195 | y: 0,
196 | width: frame.size.width,
197 | height: frame.size.height),
198 | color: pointColor,
199 | lineWidth: lineWidth)
200 | addSublayer(check!)
201 | }
202 | }
203 |
--------------------------------------------------------------------------------
/CRRefresh/CRRefresh/Animators/FastAnimator/FastLayer.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FastLayer.swift
3 | // CRRefresh
4 | //
5 | // **************************************************
6 | // * _____ *
7 | // * __ _ __ ___ \ / *
8 | // * \ \/ \/ / / __\ / / *
9 | // * \ _ / | (__ / / *
10 | // * \/ \/ \___/ / /__ *
11 | // * /_____/ *
12 | // * *
13 | // **************************************************
14 | // Github :https://github.com/imwcl
15 | // HomePage:https://imwcl.com
16 | // CSDN :http://blog.csdn.net/wang631106979
17 | //
18 | // Created by 王崇磊 on 16/9/14.
19 | // Copyright © 2016年 王崇磊. All rights reserved.
20 | //
21 | // @class FastLayer
22 | // @abstract FastLayer
23 | // @discussion FastLayer
24 | //
25 |
26 | import UIKit
27 |
28 | class FastLayer: CALayer {
29 |
30 | private (set)var circle: FastCircleLayer?
31 |
32 | private (set)var arrow: FastArrowLayer?
33 |
34 | let color: UIColor
35 |
36 | let arrowColor: UIColor
37 |
38 | let lineWidth: CGFloat
39 |
40 | //MARK: Public Methods
41 |
42 |
43 | //MARK: Override
44 |
45 |
46 | //MARK: Initial Methods
47 | init(frame: CGRect, color: UIColor = .init(rgb: (214, 214, 214)), arrowColor: UIColor = .init(rgb: (165, 165, 165)), lineWidth: CGFloat = 1) {
48 | self.color = color
49 | self.arrowColor = arrowColor
50 | self.lineWidth = lineWidth
51 | super.init()
52 | self.frame = frame
53 | backgroundColor = UIColor.clear.cgColor
54 | initCircle()
55 | initArrowLayer()
56 | }
57 |
58 | required init?(coder aDecoder: NSCoder) {
59 | fatalError("init(coder:) has not been implemented")
60 | }
61 |
62 | //MARK: Privater Methods
63 | private func initCircle() {
64 | circle = FastCircleLayer(frame: bounds, color: color, pointColor: arrowColor, lineWidth: lineWidth)
65 | addSublayer(circle!)
66 | }
67 |
68 | private func initArrowLayer() {
69 | arrow = FastArrowLayer(frame: bounds, color: arrowColor, lineWidth: lineWidth)
70 | addSublayer(arrow!)
71 | }
72 |
73 | }
74 |
--------------------------------------------------------------------------------
/CRRefresh/CRRefresh/Animators/NormalAnimator/NormalFooter.bundle/en.lproj/Localizable.strings:
--------------------------------------------------------------------------------
1 | "CRRefreshFooterIdleText" = "Loading more";
2 | "CRRefreshFooterNoMoreText" = "No more data";
3 | "CRRefreshFooterRefreshingText" = "Loading...";
4 |
--------------------------------------------------------------------------------
/CRRefresh/CRRefresh/Animators/NormalAnimator/NormalFooter.bundle/zh.lproj/Localizable.strings:
--------------------------------------------------------------------------------
1 | "CRRefreshFooterIdleText" = "上拉加载更多";
2 | "CRRefreshFooterNoMoreText" = "没有更多数据";
3 | "CRRefreshFooterRefreshingText" = "正在加载更多的数据...";
4 |
5 |
--------------------------------------------------------------------------------
/CRRefresh/CRRefresh/Animators/NormalAnimator/NormalFooterAnimator.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NormalFooterAnimator.swift
3 | // CRRefresh
4 | //
5 | // **************************************************
6 | // * _____ *
7 | // * __ _ __ ___ \ / *
8 | // * \ \/ \/ / / __\ / / *
9 | // * \ _ / | (__ / / *
10 | // * \/ \/ \___/ / /__ *
11 | // * /_____/ *
12 | // * *
13 | // **************************************************
14 | // Github :https://github.com/imwcl
15 | // HomePage:http://imwcl.com
16 | // CSDN :http://blog.csdn.net/wang631106979
17 | //
18 | // Created by 王崇磊 on 16/9/14.
19 | // Copyright © 2016年 王崇磊. All rights reserved.
20 | //
21 | // @class NormalFooterAnimator
22 | // @abstract 普通底部加载Animator
23 | // @discussion 普通底部加载Animator
24 | //
25 |
26 | import UIKit
27 |
28 | open class NormalFooterAnimator: UIView, CRRefreshProtocol {
29 |
30 | static let crBundle = CRRefreshBundle.bundle(name: "NormalFooter", for: NormalFooterAnimator.self)
31 |
32 | open var loadingMoreDescription = crBundle?.localizedString(key: "CRRefreshFooterIdleText")
33 | open var noMoreDataDescription = crBundle?.localizedString(key: "CRRefreshFooterNoMoreText")
34 | open var loadingDescription = crBundle?.localizedString(key: "CRRefreshFooterRefreshingText")
35 |
36 | open var view: UIView { return self }
37 | open var duration: TimeInterval = 0.3
38 | open var insets: UIEdgeInsets = .zero
39 | open var trigger: CGFloat = 50.0
40 | open var execute: CGFloat = 50.0
41 | open var endDelay: CGFloat = 0
42 | open var hold: CGFloat = 50
43 |
44 | fileprivate lazy var titleLabel: UILabel = {
45 | let label = UILabel.init(frame: CGRect.zero)
46 | label.font = UIFont.systemFont(ofSize: 14.0)
47 | label.textColor = UIColor.init(white: 160.0 / 255.0, alpha: 1.0)
48 | label.textAlignment = .center
49 | return label
50 | }()
51 |
52 | fileprivate lazy var indicatorView: UIActivityIndicatorView = {
53 | let indicatorView = UIActivityIndicatorView.init(style: .gray)
54 | indicatorView.isHidden = true
55 | return indicatorView
56 | }()
57 |
58 | public override init(frame: CGRect) {
59 | super.init(frame: frame)
60 | titleLabel.text = loadingMoreDescription
61 | addSubview(titleLabel)
62 | addSubview(indicatorView)
63 | }
64 |
65 | public required init(coder aDecoder: NSCoder) {
66 | fatalError("init(coder:) has not been implemented")
67 | }
68 |
69 | open func refreshBegin(view: CRRefreshComponent) {
70 | indicatorView.startAnimating()
71 | titleLabel.text = loadingDescription
72 | indicatorView.isHidden = false
73 | }
74 |
75 | public func refreshWillEnd(view: CRRefreshComponent) {
76 |
77 | }
78 |
79 | open func refreshEnd(view: CRRefreshComponent, finish: Bool) {
80 | indicatorView.stopAnimating()
81 | titleLabel.text = loadingMoreDescription
82 | indicatorView.isHidden = true
83 | }
84 |
85 | open func refresh(view: CRRefreshComponent, progressDidChange progress: CGFloat) {
86 |
87 | }
88 |
89 | open func refresh(view: CRRefreshComponent, stateDidChange state: CRRefreshState) {
90 | switch state {
91 | case .idle:
92 | titleLabel.text = loadingMoreDescription
93 | case .refreshing :
94 | titleLabel.text = loadingDescription
95 | break
96 | case .noMoreData:
97 | titleLabel.text = noMoreDataDescription
98 | break
99 | case .pulling:
100 | titleLabel.text = loadingMoreDescription
101 | break
102 | default:
103 | break
104 | }
105 | setNeedsLayout()
106 | }
107 |
108 | open override func layoutSubviews() {
109 | super.layoutSubviews()
110 | let s = self.bounds.size
111 | let w = s.width
112 | let h = s.height
113 | titleLabel.sizeToFit()
114 | titleLabel.center = CGPoint.init(x: w / 2.0, y: h / 2.0 - 5.0 + insets.top)
115 | indicatorView.center = CGPoint.init(x: titleLabel.frame.origin.x - 18.0, y: titleLabel.center.y)
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/CRRefresh/CRRefresh/Animators/NormalAnimator/NormalHeader.bundle/en.lproj/Localizable.strings:
--------------------------------------------------------------------------------
1 | "CRRefreshHeaderIdleText" = "Pull down to refresh";
2 | "CRRefreshHeaderPullingText" = "Release to refresh";
3 | "CRRefreshHeaderRefreshingText" = "Loading...";
4 |
--------------------------------------------------------------------------------
/CRRefresh/CRRefresh/Animators/NormalAnimator/NormalHeader.bundle/images/refresh_arrow@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CRAnimation/CRRefresh/336441963819645dc7427352664f114f5a4cc864/CRRefresh/CRRefresh/Animators/NormalAnimator/NormalHeader.bundle/images/refresh_arrow@2x.png
--------------------------------------------------------------------------------
/CRRefresh/CRRefresh/Animators/NormalAnimator/NormalHeader.bundle/images/refresh_arrow@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CRAnimation/CRRefresh/336441963819645dc7427352664f114f5a4cc864/CRRefresh/CRRefresh/Animators/NormalAnimator/NormalHeader.bundle/images/refresh_arrow@3x.png
--------------------------------------------------------------------------------
/CRRefresh/CRRefresh/Animators/NormalAnimator/NormalHeader.bundle/zh.lproj/Localizable.strings:
--------------------------------------------------------------------------------
1 | "CRRefreshHeaderIdleText" = "下拉可以刷新";
2 | "CRRefreshHeaderPullingText" = "松开立即刷新";
3 | "CRRefreshHeaderRefreshingText" = "正在刷新数据中...";
4 |
--------------------------------------------------------------------------------
/CRRefresh/CRRefresh/Animators/NormalAnimator/NormalHeaderAnimator.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NormalHeaderAnimator.swift
3 | // CRRefresh
4 | //
5 | // **************************************************
6 | // * _____ *
7 | // * __ _ __ ___ \ / *
8 | // * \ \/ \/ / / __\ / / *
9 | // * \ _ / | (__ / / *
10 | // * \/ \/ \___/ / /__ *
11 | // * /_____/ *
12 | // * *
13 | // **************************************************
14 | // Github :https://github.com/imwcl
15 | // HomePage:http://imwcl.com
16 | // CSDN :http://blog.csdn.net/wang631106979
17 | //
18 | // Created by 王崇磊 on 16/9/14.
19 | // Copyright © 2016年 王崇磊. All rights reserved.
20 | //
21 | // @class NormalFootAnimator
22 | // @abstract 普通顶部加载Animator
23 | // @discussion 普通顶部加载Animator
24 | //
25 |
26 | import UIKit
27 |
28 | open class NormalHeaderAnimator: UIView, CRRefreshProtocol {
29 |
30 | static let crBundle = CRRefreshBundle.bundle(name: "NormalHeader", for: NormalHeaderAnimator.self)
31 |
32 | open var pullToRefreshDescription = crBundle?.localizedString(key: "CRRefreshHeaderIdleText") {
33 | didSet {
34 | if pullToRefreshDescription != oldValue {
35 | titleLabel.text = pullToRefreshDescription;
36 | }
37 | }
38 | }
39 | open var releaseToRefreshDescription = crBundle?.localizedString(key: "CRRefreshHeaderPullingText")
40 | open var loadingDescription = crBundle?.localizedString(key: "CRRefreshHeaderRefreshingText")
41 |
42 | open var view: UIView { return self }
43 | open var insets: UIEdgeInsets = .zero
44 | open var trigger: CGFloat = 60.0
45 | open var execute: CGFloat = 60.0
46 | open var endDelay: CGFloat = 0
47 | public var hold: CGFloat = 60
48 |
49 | fileprivate let imageView: UIImageView = {
50 | let imageView = UIImageView.init()
51 | imageView.image = crBundle?.imageFromBundle("refresh_arrow")
52 | return imageView
53 | }()
54 |
55 | fileprivate let titleLabel: UILabel = {
56 | let label = UILabel.init(frame: CGRect.zero)
57 | label.font = UIFont.systemFont(ofSize: 14.0)
58 | label.textColor = UIColor.init(white: 0.625, alpha: 1.0)
59 | label.textAlignment = .left
60 | return label
61 | }()
62 |
63 | fileprivate let indicatorView: UIActivityIndicatorView = {
64 | let indicatorView = UIActivityIndicatorView.init(style: .gray)
65 | indicatorView.isHidden = true
66 | return indicatorView
67 | }()
68 |
69 | public override init(frame: CGRect) {
70 | super.init(frame: frame)
71 | titleLabel.text = pullToRefreshDescription
72 | self.addSubview(imageView)
73 | self.addSubview(titleLabel)
74 | self.addSubview(indicatorView)
75 | }
76 |
77 | public required init(coder aDecoder: NSCoder) {
78 | fatalError("init(coder:) has not been implemented")
79 | }
80 |
81 | open func refreshBegin(view: CRRefreshComponent) {
82 | indicatorView.startAnimating()
83 | indicatorView.isHidden = false
84 | imageView.isHidden = true
85 | titleLabel.text = loadingDescription
86 | imageView.transform = CGAffineTransform(rotationAngle: 0.000001 - CGFloat(Double.pi))
87 | }
88 |
89 | open func refreshEnd(view: CRRefreshComponent, finish: Bool) {
90 | if finish {
91 | indicatorView.stopAnimating()
92 | indicatorView.isHidden = true
93 | imageView.isHidden = false
94 | imageView.transform = CGAffineTransform.identity
95 | }else {
96 | titleLabel.text = pullToRefreshDescription
97 | setNeedsLayout()
98 | }
99 | }
100 |
101 | public func refreshWillEnd(view: CRRefreshComponent) {
102 |
103 | }
104 |
105 | open func refresh(view: CRRefreshComponent, progressDidChange progress: CGFloat) {
106 |
107 | }
108 |
109 | open func refresh(view: CRRefreshComponent, stateDidChange state: CRRefreshState) {
110 | switch state {
111 | case .refreshing:
112 | titleLabel.text = loadingDescription
113 | setNeedsLayout()
114 | break
115 | case .pulling:
116 | titleLabel.text = releaseToRefreshDescription
117 | self.setNeedsLayout()
118 | UIView.animate(withDuration: 0.2, delay: 0.0, options: UIView.AnimationOptions(), animations: {
119 | [weak self] in
120 | self?.imageView.transform = CGAffineTransform(rotationAngle: 0.000001 - CGFloat(Double.pi))
121 | }) { (animated) in }
122 | break
123 | case .idle:
124 | titleLabel.text = pullToRefreshDescription
125 | self.setNeedsLayout()
126 | UIView.animate(withDuration: 0.2, delay: 0.0, options: UIView.AnimationOptions(), animations: {
127 | [weak self] in
128 | self?.imageView.transform = CGAffineTransform.identity
129 | }) { (animated) in }
130 | break
131 | default:
132 | break
133 | }
134 | }
135 |
136 | open override func layoutSubviews() {
137 | super.layoutSubviews()
138 | let s = bounds.size
139 | let w = s.width
140 | let h = s.height
141 |
142 | UIView.performWithoutAnimation {
143 | titleLabel.sizeToFit()
144 | titleLabel.center = .init(x: w / 2.0, y: h / 2.0)
145 | indicatorView.center = .init(x: titleLabel.frame.origin.x - 16.0, y: h / 2.0)
146 | imageView.frame = CGRect.init(x: titleLabel.frame.origin.x - 28.0, y: (h - 18.0) / 2.0, width: 18.0, height: 18.0)
147 | }
148 | }
149 |
150 | }
151 |
--------------------------------------------------------------------------------
/CRRefresh/CRRefresh/Animators/RamotionAnimator/RamotionAnimator.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RamotionAnimator.swift
3 | // CRRefresh
4 | //
5 | // **************************************************
6 | // * _____ *
7 | // * __ _ __ ___ \ / *
8 | // * \ \/ \/ / / __\ / / *
9 | // * \ _ / | (__ / / *
10 | // * \/ \/ \___/ / /__ *
11 | // * /_____/ *
12 | // * *
13 | // **************************************************
14 | // Github :https://github.com/imwcl
15 | // HomePage:http://imwcl.com
16 | // CSDN :http://blog.csdn.net/wang631106979
17 | //
18 | // Created by 王崇磊 on 16/9/14.
19 | // Copyright © 2016年 王崇磊. All rights reserved.
20 | //
21 | // @class RamotionAnimator
22 | // @abstract Ramotion动画的Animator
23 | // @discussion Ramotion动画的Animator
24 | //
25 |
26 | import UIKit
27 |
28 | open class RamotionAnimator: UIView, CRRefreshProtocol {
29 |
30 | open var view: UIView { return self }
31 |
32 | open var insets: UIEdgeInsets = .zero
33 |
34 | open var trigger: CGFloat = 140
35 |
36 | open var execute: CGFloat = 90
37 |
38 | open var endDelay: CGFloat = 0
39 |
40 | open var hold: CGFloat = 90
41 |
42 |
43 | var bounceLayer: RamotionBounceLayer?
44 | /// 上方wave的颜色
45 | let waveColor: UIColor
46 | /// 球的颜色
47 | let ballColor: UIColor
48 |
49 | deinit {
50 | bounceLayer?.clear()
51 | }
52 |
53 | /// 初始化方法
54 | ///
55 | /// - Parameters:
56 | /// - ballColor: 小球的颜色
57 | /// - waveColor: 上方wave的颜色
58 | public init(ballColor: UIColor = .white,
59 | waveColor: UIColor = .init(rgb: (140, 141, 178))) {
60 | self.ballColor = ballColor
61 | self.waveColor = waveColor
62 | super.init(frame: .zero)
63 | }
64 |
65 | func bounceLayer(view: CRRefreshComponent) -> RamotionBounceLayer? {
66 | if bounceLayer?.superlayer == nil {
67 | if let scrollView = view.scrollView {
68 | bounceLayer = RamotionBounceLayer(frame: scrollView.bounds, execute: execute, ballColor: ballColor, waveColor: waveColor)
69 | if let superView = scrollView.superview {
70 | superView.layer.addSublayer(bounceLayer!)
71 | }
72 | }
73 | }
74 | return bounceLayer
75 | }
76 |
77 | open func refreshBegin(view: CRRefreshComponent) {
78 | bounceLayer(view: view)?.wave(execute)
79 | bounceLayer(view: view)?.startAnimation()
80 | }
81 |
82 | open func refreshEnd(view: CRRefreshComponent, finish: Bool) {
83 | if !finish {
84 | bounceLayer(view: view)?.endAnimation()
85 | }else {
86 | bounceLayer(view: view)?.ballLayer.isHidden = true
87 | bounceLayer(view: view)?.linkLayer.isHidden = true
88 | bounceLayer(view: view)?.wavelayer.endAnimation()
89 | }
90 | }
91 |
92 | open func refresh(view: CRRefreshComponent, progressDidChange progress: CGFloat) {
93 | let offY = trigger * progress
94 | bounceLayer(view: view)?.wave(offY)
95 | }
96 |
97 | open func refresh(view: CRRefreshComponent, stateDidChange state: CRRefreshState) {
98 |
99 | }
100 |
101 | open func refreshWillEnd(view: CRRefreshComponent) {
102 |
103 | }
104 |
105 | public override init(frame: CGRect) {
106 | self.ballColor = .white
107 | self.waveColor = .init(rgb: (140, 141, 178))
108 | super.init(frame: frame)
109 | }
110 |
111 | required public init?(coder aDecoder: NSCoder) {
112 | fatalError("init(coder:) has not been implemented")
113 | }
114 | }
115 |
--------------------------------------------------------------------------------
/CRRefresh/CRRefresh/Animators/RamotionAnimator/RamotionBallLayer.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RamotionBallLayer.swift
3 | // CRRefresh
4 | //
5 | // **************************************************
6 | // * _____ *
7 | // * __ _ __ ___ \ / *
8 | // * \ \/ \/ / / __\ / / *
9 | // * \ _ / | (__ / / *
10 | // * \/ \/ \___/ / /__ *
11 | // * /_____/ *
12 | // * *
13 | // **************************************************
14 | // Github :https://github.com/imwcl
15 | // HomePage:https://imwcl.com
16 | // CSDN :http://blog.csdn.net/wang631106979
17 | //
18 | // Created by 王崇磊 on 16/9/14.
19 | // Copyright © 2016年 王崇磊. All rights reserved.
20 | //
21 | // @class RamotionBallLayer
22 | // @abstract RamotionBallLayer
23 | // @discussion RamotionBallLayer
24 | //
25 |
26 | import UIKit
27 |
28 | private var timeFunc = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeOut)
29 |
30 | private var upDuration: Double = 0.5
31 |
32 | class RamotionBallLayer: CALayer {
33 |
34 | deinit {
35 | circleLayer.stopAnimation()
36 | }
37 |
38 | var circleLayer: CircleLayer!
39 |
40 | init(frame: CGRect, duration: CFTimeInterval, moveUpDist: CGFloat, color: UIColor = .white) {
41 | upDuration = duration
42 | super.init()
43 | self.frame = frame
44 | let circleWidth = min(frame.size.width, frame.size.height)
45 | circleLayer = CircleLayer(size: circleWidth, moveUpDist: moveUpDist, frame: frame, color: color)
46 | addSublayer(circleLayer)
47 | isHidden = true
48 | }
49 |
50 | override init(layer: Any) {
51 | super.init(layer: layer)
52 | }
53 |
54 | required init?(coder aDecoder: NSCoder) {
55 | fatalError("init(coder:) has not been implemented")
56 | }
57 |
58 | func startAnimation() {
59 | circleLayer.startAnimation()
60 | }
61 |
62 | func endAnimation(_ complition: (()-> Void)? = nil) {
63 | circleLayer.endAnimation(complition)
64 | }
65 | }
66 |
67 | class CircleLayer :CAShapeLayer, CAAnimationDelegate {
68 |
69 | deinit {
70 | spiner?.stopAnimation()
71 | }
72 |
73 | var moveUpDist: CGFloat = 0
74 | var spiner: SpinerLayer?
75 | var didEndAnimation: (() -> Void)?
76 |
77 | init(size: CGFloat, moveUpDist: CGFloat, frame: CGRect, color: UIColor = UIColor.white) {
78 | self.moveUpDist = moveUpDist
79 | let selfFrame = CGRect(x: 0, y: 0, width: frame.size.width, height: frame.size.height)
80 | spiner = SpinerLayer(superLayerFrame: selfFrame, ballSize: size, color: color)
81 | super.init()
82 |
83 | addSublayer(spiner!)
84 |
85 | let radius:CGFloat = size / 2
86 | self.frame = selfFrame
87 | let center = CGPoint(x: frame.size.width / 2, y: frame.size.height/2)
88 | let startAngle = 0 - Double.pi/2
89 | let endAngle = Double.pi * 2 - Double.pi/2
90 | let clockwise: Bool = true
91 | self.path = UIBezierPath(arcCenter: center, radius: radius, startAngle: CGFloat(startAngle), endAngle: CGFloat(endAngle), clockwise: clockwise).cgPath
92 | self.fillColor = color.withAlphaComponent(1).cgColor
93 | self.strokeColor = self.fillColor
94 | self.lineWidth = 0
95 | self.strokeEnd = 1
96 | }
97 |
98 | override init(layer: Any) {
99 | super.init(layer: layer)
100 | }
101 |
102 | required init?(coder aDecoder: NSCoder) {
103 | fatalError("init(coder:) has not been implemented")
104 | }
105 |
106 | func startAnimation() {
107 | moveUp(moveUpDist)
108 | DispatchQueue.main.asyncAfter(deadline: .now() + upDuration) { [weak self] in
109 | self?.spiner?.animation()
110 | }
111 | }
112 |
113 | func endAnimation(_ complition:(()->())? = nil) {
114 | spiner?.stopAnimation()
115 | moveDown(moveUpDist)
116 | didEndAnimation = complition
117 | }
118 |
119 | func stopAnimation() {
120 | spiner?.stopAnimation()
121 | removeAllAnimations()
122 | didEndAnimation?()
123 | }
124 |
125 | func moveUp(_ distance: CGFloat) {
126 | let move = CABasicAnimation(keyPath: "position")
127 |
128 | move.fromValue = NSValue(cgPoint: position)
129 | move.toValue = NSValue(cgPoint: CGPoint(x: position.x,
130 | y: position.y - distance))
131 |
132 | move.duration = upDuration
133 | move.timingFunction = timeFunc
134 |
135 | move.fillMode = CAMediaTimingFillMode.forwards
136 | move.isRemovedOnCompletion = false
137 | add(move, forKey: move.keyPath)
138 | }
139 |
140 | func moveDown(_ distance: CGFloat) {
141 | let move = CABasicAnimation(keyPath: "position")
142 |
143 | move.fromValue = NSValue(cgPoint: CGPoint(x: position.x, y: position.y - distance))
144 | move.toValue = NSValue(cgPoint: position)
145 |
146 | move.duration = upDuration
147 | move.timingFunction = timeFunc
148 |
149 | move.fillMode = CAMediaTimingFillMode.forwards
150 | move.isRemovedOnCompletion = false
151 | move.delegate = self
152 | add(move, forKey: move.keyPath)
153 | }
154 |
155 | func animationDidStop(_ anim: CAAnimation, finished flag: Bool) {
156 | didEndAnimation?()
157 | }
158 | }
159 |
160 |
161 | class SpinerLayer: CAShapeLayer, CAAnimationDelegate {
162 |
163 | init(superLayerFrame: CGRect, ballSize: CGFloat, color: UIColor = UIColor.white) {
164 | super.init()
165 |
166 | let radius:CGFloat = (ballSize / 2) * 1.2//1.45
167 | self.frame = CGRect(x: 0, y: 0, width: superLayerFrame.height, height: superLayerFrame.height)
168 | let center = CGPoint(x: superLayerFrame.size.width / 2, y: superLayerFrame.origin.y + superLayerFrame.size.height/2)
169 | let startAngle = 0 - Double.pi/2
170 | let endAngle = (Double.pi * 2 - Double.pi/2) + Double.pi / 8
171 | let clockwise: Bool = true
172 | self.path = UIBezierPath(arcCenter: center, radius: radius, startAngle: CGFloat(startAngle), endAngle: CGFloat(endAngle), clockwise: clockwise).cgPath
173 |
174 | self.fillColor = nil
175 | self.strokeColor = color.cgColor
176 | self.lineWidth = 2
177 | self.lineCap = CAShapeLayerLineCap.round
178 |
179 | self.strokeStart = 0
180 | self.strokeEnd = 0
181 | self.isHidden = true
182 |
183 | }
184 |
185 | required init?(coder aDecoder: NSCoder) {
186 | fatalError("init(coder:) has not been implemented")
187 | }
188 |
189 | func animation() {
190 | self.isHidden = false
191 | let rotate = CABasicAnimation(keyPath: "transform.rotation.z")
192 | rotate.fromValue = 0
193 | rotate.toValue = Double.pi * 2
194 | rotate.duration = 1
195 | rotate.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear)
196 | rotate.repeatCount = HUGE
197 | rotate.fillMode = CAMediaTimingFillMode.forwards
198 | rotate.isRemovedOnCompletion = false
199 | self.add(rotate, forKey: rotate.keyPath)
200 |
201 | strokeEndAnimation()
202 | }
203 |
204 | func strokeEndAnimation() {
205 | let endPoint = CABasicAnimation(keyPath: "strokeEnd")
206 | endPoint.fromValue = 0
207 | endPoint.toValue = 1
208 | endPoint.duration = 1.8
209 | endPoint.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeOut)
210 | endPoint.repeatCount = HUGE
211 | endPoint.fillMode = CAMediaTimingFillMode.forwards
212 | endPoint.isRemovedOnCompletion = false
213 | endPoint.delegate = self
214 | add(endPoint, forKey: endPoint.keyPath)
215 | }
216 |
217 | func strokeStartAnimation() {
218 | let startPoint = CABasicAnimation(keyPath: "strokeStart")
219 | startPoint.fromValue = 0
220 | startPoint.toValue = 1
221 | startPoint.duration = 0.8
222 | startPoint.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeOut)
223 | startPoint.repeatCount = HUGE
224 | startPoint.delegate = self
225 | add(startPoint, forKey: startPoint.keyPath)
226 | }
227 |
228 | func animationDidStop(_ anim: CAAnimation, finished flag: Bool) {
229 | if self.isHidden == false {
230 | let a:CABasicAnimation = anim as! CABasicAnimation
231 | if a.keyPath == "strokeStart" {
232 | strokeEndAnimation()
233 | }else if a.keyPath == "strokeEnd" {
234 | strokeStartAnimation()
235 | }
236 | }
237 | }
238 |
239 | func stopAnimation() {
240 | isHidden = true
241 | removeAllAnimations()
242 | }
243 | }
244 |
245 |
--------------------------------------------------------------------------------
/CRRefresh/CRRefresh/Animators/RamotionAnimator/RamotionBounceLayer.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RamotionBounceLayer.swift
3 | // CRRefresh
4 | //
5 | // **************************************************
6 | // * _____ *
7 | // * __ _ __ ___ \ / *
8 | // * \ \/ \/ / / __\ / / *
9 | // * \ _ / | (__ / / *
10 | // * \/ \/ \___/ / /__ *
11 | // * /_____/ *
12 | // * *
13 | // **************************************************
14 | // Github :https://github.com/imwcl
15 | // HomePage:https://imwcl.com
16 | // CSDN :http://blog.csdn.net/wang631106979
17 | //
18 | // Created by 王崇磊 on 16/9/14.
19 | // Copyright © 2016年 王崇磊. All rights reserved.
20 | //
21 | // @class RamotionBounceLayer
22 | // @abstract 背景layer
23 | // @discussion 背景layer
24 | //
25 |
26 | import UIKit
27 |
28 | class RamotionBounceLayer: CALayer {
29 | // 动画时间
30 | var animDuration: CFTimeInterval = 0.45
31 | // 白色渐变的背景
32 | let backLayer = CALayer()
33 | // 上方的变动的背景
34 | let wavelayer: RamotionWaveLayer
35 | // 球
36 | let ballLayer: RamotionBallLayer
37 | // 连接线
38 | let linkLayer: CAShapeLayer = CAShapeLayer()
39 | // 执行动画的高度
40 | var execute: CGFloat = 0
41 | // 计时器
42 | var displayLink: CADisplayLink?
43 | // 记录之前的offY
44 | private var previousOffY: CGFloat = 0
45 | /// 上方wave的颜色
46 | let waveColor: UIColor
47 | /// 球的颜色
48 | let ballColor: UIColor
49 |
50 | deinit {
51 | displayLink?.invalidate()
52 | displayLink = nil
53 | }
54 |
55 | //MARK: Initial Methods
56 | init(frame: CGRect, execute: CGFloat, ballColor: UIColor, waveColor: UIColor) {
57 | self.waveColor = waveColor
58 | self.ballColor = ballColor
59 | self.execute = execute
60 | wavelayer = .init(frame: .init(origin: .zero, size: frame.size), execute: execute, bounceDuration: animDuration, color: waveColor)
61 | ballLayer = .init(frame: .init(x: frame.width/2 - 20, y: execute + 40, width: 40, height: 40), duration: animDuration, moveUpDist: 60 + execute/2, color: ballColor)
62 | linkLayer.fillColor = ballColor.cgColor
63 | super.init()
64 | backgroundColor = UIColor.clear.cgColor
65 | backLayer.frame = .init(origin: .zero, size: frame.size)
66 | backLayer.backgroundColor = ballColor.cgColor
67 | backLayer.opacity = 0
68 | addSublayer(backLayer)
69 | addSublayer(wavelayer)
70 | addSublayer(ballLayer)
71 | addSublayer(linkLayer)
72 | }
73 |
74 | required init?(coder aDecoder: NSCoder) {
75 | fatalError("init(coder:) has not been implemented")
76 | }
77 |
78 |
79 | //MARK: Public Methods
80 | func wave(_ y: CGFloat) {
81 | wavelayer.wave(y, execute: execute)
82 | let progress = y/execute
83 | backLayer.opacity = Float(progress)
84 | }
85 |
86 | func startAnimation() {
87 | ballLayer.isHidden = false
88 | linkLayer.isHidden = false
89 | addDisPlay()
90 | wavelayer.startAnimation()
91 | ballLayer.startAnimation()
92 | }
93 |
94 | func endAnimation() {
95 | ballLayer.endAnimation { [weak self] in
96 | self?.removeDisPlay()
97 | self?.wave(0)
98 | }
99 | }
100 |
101 | func clear() {
102 | displayLink?.invalidate()
103 | displayLink = nil
104 | }
105 |
106 | //MARK: Private Methods
107 | @objc private func displayAction() {
108 | let offY = ballLayer.circleLayer.presentation()?.frame.origin.y
109 | let frame1 = ballLayer.frame
110 | let frame2 = wavelayer.reference.layer.presentation()?.frame
111 | if let offY = offY, let frame2 = frame2 {
112 | DispatchQueue.global().async {
113 | // 判断是球是向上还是下,false为上,速度快时,获取的位置不及时,向下时需要调整位置
114 | let isIncrement = (offY - self.previousOffY) > 0
115 | let path = UIBezierPath()
116 | let x1 = frame1.origin.x + (isIncrement ? 4 : 0)
117 | let y1 = frame1.origin.y + offY
118 | let w1 = frame1.size.width - (isIncrement ? 8 : 0)
119 | let h1 = frame1.size.height
120 | let x2 = frame2.origin.x
121 | let y2 = frame2.origin.y
122 | let w2 = frame2.size.width
123 | let h2 = frame2.size.height
124 | let subY = y2 - y1
125 | // y1和y2的间距
126 | let subScale = subY/self.execute/2
127 | // 断开的距离为10
128 | let executeSub = self.ballLayer.circleLayer.moveUpDist + offY
129 | if executeSub < 10 {
130 | if !isIncrement {
131 | let executeSubScale = executeSub/10
132 | path.move(to: .init(x: x1 - 15, y: y2 + h2/2 + 15))
133 | path.addLine(to: .init(x: x1 + w1 + 15, y: y2 + h2/2 + 15))
134 | path.addQuadCurve(to: .init(x: x1 - 15, y: y2 + h2/2 + 15), controlPoint: .init(x: x1 + w1/2, y: y2 + h2/2 - self.execute/6 * executeSubScale))
135 | }
136 | }else {
137 | path.move(to: .init(x: x2 , y: y2 + h2))
138 | path.addLine(to: .init(x: x2 + w2, y: y2 + h2))
139 | path.addQuadCurve(to: .init(x: x1 + w1, y: y1 + h1/2), controlPoint: .init(x: x1 + w1 - w1*2*subScale, y: y1 + (y2 - y1)/2 + h1/2 + h2/2))
140 | path.addLine(to: .init(x: x1, y: y1 + h1/2))
141 | path.addQuadCurve(to: .init(x: x2 , y: y2 + h2), controlPoint: .init(x: x1 + w1*2*subScale, y: y1 + (y2 - y1)/2 + h1/2 + h2/2))
142 | if y1 + h1 <= self.execute, isIncrement {
143 | DispatchQueue.main.async {
144 | self.wavelayer.startDownAnimation()
145 | }
146 | }
147 | }
148 | DispatchQueue.main.async {
149 | self.linkLayer.path = path.cgPath
150 | }
151 | self.previousOffY = offY
152 | }
153 | }
154 | }
155 |
156 | private func addDisPlay() {
157 | displayLink = CADisplayLink(target: self, selector: #selector(displayAction))
158 | displayLink?.add(to: .main, forMode: .common)
159 | }
160 |
161 | private func removeDisPlay() {
162 | displayLink?.invalidate()
163 | displayLink = nil
164 | }
165 | }
166 |
--------------------------------------------------------------------------------
/CRRefresh/CRRefresh/Animators/RamotionAnimator/RamotionWaveLayer.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RamotionWaveLayer.swift
3 | // CRRefresh
4 | //
5 | // **************************************************
6 | // * _____ *
7 | // * __ _ __ ___ \ / *
8 | // * \ \/ \/ / / __\ / / *
9 | // * \ _ / | (__ / / *
10 | // * \/ \/ \___/ / /__ *
11 | // * /_____/ *
12 | // * *
13 | // **************************************************
14 | // Github :https://github.com/imwcl
15 | // HomePage:https://imwcl.com
16 | // CSDN :http://blog.csdn.net/wang631106979
17 | //
18 | // Created by 王崇磊 on 16/9/14.
19 | // Copyright © 2016年 王崇磊. All rights reserved.
20 | //
21 | // @class RamotionWaveLayer
22 | // @abstract 上方的wavelayer
23 | // @discussion 上方的wavelayer
24 | //
25 |
26 | import UIKit
27 |
28 | private let referenceWitdh: CGFloat = 150
29 | private let referenceHeight: CGFloat = 50
30 |
31 | class RamotionWaveLayer: CALayer, CAAnimationDelegate {
32 |
33 | // 上方的wavelayer
34 | let waveLayer: CAShapeLayer = CAShapeLayer()
35 | // 参考的UIView
36 | let reference: UIView = UIView(frame: .init(x: 0, y: 0, width: referenceWitdh, height: referenceHeight))
37 | // 动画时间
38 | var bounceDuration: CFTimeInterval
39 | // layer的颜色
40 | let color: UIColor
41 | // 执行动画的高度
42 | var execute: CGFloat
43 | // 是否在动画中
44 | var isAnimation: Bool = false
45 | // 计时器
46 | var displayLink: CADisplayLink?
47 |
48 | deinit {
49 | displayLink?.invalidate()
50 | displayLink = nil
51 | }
52 |
53 | //MARK: Initial Methods
54 | init(frame: CGRect, execute: CGFloat, bounceDuration: CFTimeInterval = 0.45, color: UIColor = .init(rgb: (140, 141, 178))) {
55 | self.bounceDuration = bounceDuration
56 | self.color = color
57 | self.execute = execute
58 | super.init()
59 | self.frame = frame
60 | backgroundColor = UIColor.clear.cgColor
61 | initWave()
62 | initReferenceLayer()
63 | }
64 |
65 | required init?(coder aDecoder: NSCoder) {
66 | fatalError("init(coder:) has not been implemented")
67 | }
68 |
69 | func initWave() {
70 | waveLayer.lineWidth = 0
71 | waveLayer.path = wavePath(x: 0.0, y: 0.0)
72 | waveLayer.strokeColor = color.cgColor
73 | waveLayer.fillColor = color.cgColor
74 | addSublayer(waveLayer)
75 | }
76 |
77 | func initReferenceLayer() {
78 | let w = frame.size.width
79 | reference.isUserInteractionEnabled = false
80 | reference.frame = .init(x: w/2 - referenceWitdh/2, y: -referenceHeight/2, width: referenceWitdh, height: referenceHeight)
81 | reference.backgroundColor = UIColor.clear
82 | waveLayer.addSublayer(reference.layer)
83 | var trans = CGAffineTransform.identity
84 | trans = trans.translatedBy(x: 0, y: execute)
85 | reference.transform = trans
86 | }
87 |
88 | //MARK: Public Methods
89 | func wave(_ y: CGFloat, execute: CGFloat) {
90 | self.execute = execute
91 | waveLayer.path = wavePath(x: 0, y: y)
92 | if !isAnimation {
93 | var trans = CGAffineTransform.identity
94 | trans = trans.translatedBy(x: 0, y: y)
95 | reference.transform = trans
96 | }
97 | }
98 |
99 | func startAnimation() {
100 | isAnimation = true
101 | addDisPlay()
102 | boundAnimation(x: 0, y: execute)
103 | }
104 |
105 | func startDownAnimation() {
106 | if !isAnimation {
107 | isAnimation = true
108 | addDisPlay()
109 | boundDownAnimation(x: 0, y: execute)
110 | }
111 | }
112 |
113 | func endAnimation() {
114 | endBoundAnimation()
115 | }
116 |
117 | //MARK: Privater Methods
118 | private func wavePath(x: CGFloat, y: CGFloat) -> CGPath {
119 | let w = frame.width
120 | let path = UIBezierPath()
121 | if y < execute {
122 | path.move(to: .zero)
123 | path.addLine(to: .init(x: w, y: 0))
124 | path.addLine(to: .init(x: w, y: y))
125 | path.addLine(to: .init(x: 0, y: y))
126 | path.addLine(to: .zero)
127 | } else {
128 | path.move(to: .zero)
129 | path.addLine(to: .init(x: w, y: 0))
130 | path.addLine(to: .init(x: w, y: execute))
131 | path.addQuadCurve(to: .init(x: 0, y: execute), controlPoint: .init(x: w/2, y: y))
132 | path.addLine(to: .zero)
133 | }
134 | return path.cgPath
135 | }
136 |
137 | private func displayWavePath(x: CGFloat, y: CGFloat) -> CGPath {
138 | let w = frame.width
139 | let path = UIBezierPath()
140 | path.move(to: .zero)
141 | path.addLine(to: .init(x: w, y: 0))
142 | path.addLine(to: .init(x: w, y: execute))
143 | path.addQuadCurve(to: .init(x: 0, y: execute), controlPoint: .init(x: w/2, y: y))
144 | path.addLine(to: .zero)
145 | return path.cgPath
146 | }
147 |
148 | @objc private func displayAction() {
149 | if let frame = reference.layer.presentation()?.frame {
150 | DispatchQueue.global().async {
151 | let path = self.displayWavePath(x: 0, y: frame.origin.y + referenceHeight/2)
152 | DispatchQueue.main.async {
153 | self.waveLayer.path = path
154 | }
155 | }
156 | }
157 | }
158 |
159 | private func addDisPlay() {
160 | displayLink = CADisplayLink(target: self, selector: #selector(displayAction))
161 | displayLink?.add(to: .main, forMode: .common)
162 | }
163 |
164 | private func removeDisPlay() {
165 | displayLink?.invalidate()
166 | displayLink = nil
167 | }
168 |
169 | private func endBoundAnimation() {
170 | let end = CABasicAnimation(keyPath: "path")
171 | end.duration = 0.25
172 | end.fromValue = wavePath(x: 0, y: execute)
173 | end.toValue = wavePath(x: 0, y: 0)
174 | waveLayer.add(end, forKey: "end")
175 | }
176 |
177 | private func boundAnimation(x: CGFloat, y: CGFloat) {
178 | let bounce = CAKeyframeAnimation(keyPath: "transform.translation.y")
179 | bounce.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeIn)
180 | bounce.duration = bounceDuration
181 | bounce.values = [
182 | reference.frame.origin.y,
183 | y * 0.5,
184 | y * 1.2,
185 | y * 0.8,
186 | y * 1.1,
187 | y
188 | ]
189 | bounce.isRemovedOnCompletion = true
190 | bounce.fillMode = CAMediaTimingFillMode.forwards
191 | bounce.delegate = self
192 | reference.layer.add(bounce, forKey: "return")
193 | }
194 |
195 | private func boundDownAnimation(x: CGFloat, y: CGFloat) {
196 | let bounce = CAKeyframeAnimation(keyPath: "transform.translation.y")
197 | bounce.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeIn)
198 | bounce.duration = bounceDuration/2
199 | bounce.values = [
200 | y,
201 | y * 1.1,
202 | y
203 | ]
204 | bounce.isRemovedOnCompletion = true
205 | bounce.fillMode = CAMediaTimingFillMode.forwards
206 | bounce.delegate = self
207 | reference.layer.add(bounce, forKey: "returnDown")
208 | }
209 |
210 | //MARK: CAAnimationDelegate
211 | func animationDidStop(_ anim: CAAnimation, finished flag: Bool) {
212 | removeDisPlay()
213 | isAnimation = false
214 | }
215 |
216 | }
217 |
--------------------------------------------------------------------------------
/CRRefresh/CRRefresh/Animators/SlackLoadingAnimator/SlackLoadingAnimator.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SlackLoadingHeaderAnimator.swift
3 | // CRRefresh
4 | //
5 | // **************************************************
6 | // * _____ *
7 | // * __ _ __ ___ \ / *
8 | // * \ \/ \/ / / __\ / / *
9 | // * \ _ / | (__ / / *
10 | // * \/ \/ \___/ / /__ *
11 | // * /_____/ *
12 | // * *
13 | // **************************************************
14 | // Github :https://github.com/imwcl
15 | // HomePage:http://imwcl.com
16 | // CSDN :http://blog.csdn.net/wang631106979
17 | //
18 | // Created by 王崇磊 on 16/9/14.
19 | // Copyright © 2016年 王崇磊. All rights reserved.
20 | //
21 | // @class SlackLoadingHeaderAnimator
22 | // @abstract SlackLoading的刷新效果
23 | // @discussion SlackLoading的刷新效果
24 | //
25 |
26 | import UIKit
27 |
28 | open class SlackLoadingAnimator: UIView, CRRefreshProtocol {
29 |
30 | open var view: UIView { return self }
31 |
32 | open var insets: UIEdgeInsets = .zero
33 |
34 | open var trigger: CGFloat = 60
35 |
36 | open var execute: CGFloat = 60
37 |
38 | open var endDelay: CGFloat = 0
39 |
40 | open var hold: CGFloat = 60
41 |
42 | var loadingView: WCLLoadingView = {
43 | let loadView = WCLLoadingView(frame: .init(x: 0, y: 0, width: 40, height: 40))
44 | loadView.isUserInteractionEnabled = false
45 | return loadView
46 | }()
47 |
48 | open func refreshBegin(view: CRRefreshComponent) {
49 | loadingView.startAnimation()
50 | }
51 |
52 | open func refreshEnd(view: CRRefreshComponent, finish: Bool) {
53 | loadingView.stopAnimation()
54 | }
55 |
56 | open func refresh(view: CRRefreshComponent, progressDidChange progress: CGFloat) {
57 |
58 | }
59 |
60 | open func refresh(view: CRRefreshComponent, stateDidChange state: CRRefreshState) {
61 |
62 | }
63 |
64 | open func refreshWillEnd(view: CRRefreshComponent) {
65 |
66 | }
67 |
68 | override init(frame: CGRect) {
69 | super.init(frame: frame)
70 | addSubview(loadingView)
71 | }
72 |
73 | required public init?(coder aDecoder: NSCoder) {
74 | fatalError("init(coder:) has not been implemented")
75 | }
76 |
77 | open override func layoutSubviews() {
78 | super.layoutSubviews()
79 | let s = bounds.size
80 | let w = s.width
81 | let h = s.height
82 | loadingView.center = .init(x: w / 2.0, y: h / 2.0)
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/CRRefresh/CRRefresh/Animators/SlackLoadingAnimator/WCLLoadingView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // WCLLoadingView.swift
3 | // WCL
4 | //
5 | // **************************************************
6 | // * _____ *
7 | // * __ _ __ ___ \ / *
8 | // * \ \/ \/ / / __\ / / *
9 | // * \ _ / | (__ / / *
10 | // * \/ \/ \___/ / /__ *
11 | // * /_____/ *
12 | // * *
13 | // **************************************************
14 | // Github :https://github.com/imwcl
15 | // HomePage:https://imwcl.com
16 | // CSDN :http://blog.csdn.net/wang631106979
17 | //
18 | // Created by 王崇磊 on 16/9/14.
19 | // Copyright © 2016年 王崇磊. All rights reserved.
20 | //
21 | // @class WCLLoadingView
22 | // @abstract Slack 的 Loading 动画
23 | // @discussion Slack 的 Loading 动画
24 | //
25 | // 动画步骤解析:http://blog.csdn.net/wang631106979/article/details/52473985
26 |
27 | import UIKit
28 |
29 | class WCLLoadingView: UIView, CAAnimationDelegate {
30 |
31 | //线的宽度
32 | var lineWidth:CGFloat = 0
33 | //线的长度
34 | var lineLength:CGFloat = 0
35 | //边距
36 | var margin:CGFloat = 0
37 | //动画时间
38 | var duration:Double = 2
39 | //动画的间隔时间
40 | var interval:Double = 1
41 | //四条线的颜色
42 | var colors:[UIColor] = [UIColor(rgb: (157, 212, 233)) , UIColor(rgb: (245, 189, 88)), UIColor(rgb: (255, 49, 126)) , UIColor(rgb: (111, 201, 181))]
43 | //动画的状态
44 | private(set) var status:AnimationStatus = .normal
45 | //四条线
46 | private var lines:[CAShapeLayer] = []
47 |
48 | enum AnimationStatus {
49 | //普通状态
50 | case normal
51 | //动画中
52 | case animating
53 | //暂停
54 | case pause
55 | }
56 |
57 | //MARK: Public Methods
58 | /**
59 | 开始动画
60 | */
61 | func startAnimation() {
62 | angleAnimation()
63 | lineAnimationOne()
64 | lineAnimationTwo()
65 | lineAnimationThree()
66 | }
67 |
68 | /**
69 | 暂停动画
70 | */
71 | func pauseAnimation() {
72 | layer.pauseAnimation()
73 | for lineLayer in lines {
74 | lineLayer.pauseAnimation()
75 | }
76 | status = .pause
77 | }
78 |
79 | /**
80 | 继续动画
81 | */
82 | func resumeAnimation() {
83 | layer.resumeAnimation()
84 | for lineLayer in lines {
85 | lineLayer.resumeAnimation()
86 | }
87 | status = .animating
88 | }
89 |
90 | /**
91 | 结束动画
92 | */
93 | func stopAnimation() {
94 | layer.removeAllAnimations()
95 | for lineLayer in lines {
96 | lineLayer.removeAllAnimations()
97 | }
98 | status = .normal
99 | }
100 |
101 | //MARK: Initial Methods
102 | convenience init(frame: CGRect , colors: [UIColor]) {
103 | self.init()
104 | self.frame = frame
105 | self.colors = colors
106 | config()
107 | }
108 |
109 | override init(frame: CGRect) {
110 | super.init(frame: frame)
111 | config()
112 | }
113 |
114 | required init?(coder aDecoder: NSCoder) {
115 | super.init(coder: aDecoder)
116 | config()
117 | }
118 |
119 | //MARK: Animation Delegate
120 | func animationDidStart(_ anim: CAAnimation) {
121 | if let animation = anim as? CABasicAnimation {
122 | if animation.keyPath == "transform.rotation.z" {
123 | status = .animating
124 | }
125 | }
126 | }
127 |
128 | func animationDidStop(_ anim: CAAnimation, finished flag: Bool) {
129 | if let animation = anim as? CABasicAnimation {
130 | if animation.keyPath == "strokeEnd" {
131 | if flag {
132 | status = .normal
133 | DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + Double(Int64(interval) * Int64(NSEC_PER_SEC)) / Double(NSEC_PER_SEC), execute: {
134 | if self.status != .animating {
135 | self.startAnimation()
136 | }
137 | })
138 | }
139 | }
140 | }
141 | }
142 |
143 | //MARK: Privater Methods
144 | //MARK: 绘制线
145 | /**
146 | 绘制四条线
147 | */
148 | private func drawLineShapeLayer() {
149 | //开始点
150 | let startPoint = [point(lineWidth/2, y: margin),
151 | point(lineLength - margin, y: lineWidth/2),
152 | point(lineLength - lineWidth/2, y: lineLength - margin),
153 | point(margin, y: lineLength - lineWidth/2)]
154 | //结束点
155 | let endPoint = [point(lineLength - lineWidth/2, y: margin) ,
156 | point(lineLength - margin, y: lineLength - lineWidth/2) ,
157 | point(lineWidth/2, y: lineLength - margin) ,
158 | point(margin, y: lineWidth/2)]
159 | for i in 0...3 {
160 | let line:CAShapeLayer = CAShapeLayer()
161 | line.lineWidth = lineWidth
162 | line.lineCap = CAShapeLayerLineCap.round
163 | line.opacity = 0.8
164 | line.strokeColor = colors[i].cgColor
165 | line.path = getLinePath(startPoint[i], endPoint: endPoint[i]).cgPath
166 | layer.addSublayer(line)
167 | lines.append(line)
168 | }
169 |
170 | }
171 |
172 | /**
173 | 获取线的路径
174 |
175 | - parameter startPoint: 开始点
176 | - parameter endPoint: 结束点
177 |
178 | - returns: 线的路径
179 | */
180 | private func getLinePath(_ startPoint: CGPoint, endPoint: CGPoint) -> UIBezierPath {
181 | let path = UIBezierPath()
182 | path.move(to: startPoint)
183 | path.addLine(to: endPoint)
184 | return path
185 | }
186 |
187 | //MARK: 动画步骤
188 | /**
189 | 旋转的动画,旋转两圈
190 | */
191 | private func angleAnimation() {
192 | let angleAnimation = CABasicAnimation.init(keyPath: "transform.rotation.z")
193 | angleAnimation.beginTime = CACurrentMediaTime()
194 | angleAnimation.fromValue = angle(-30)
195 | angleAnimation.toValue = angle(690)
196 | angleAnimation.fillMode = CAMediaTimingFillMode.forwards
197 | angleAnimation.isRemovedOnCompletion = false
198 | angleAnimation.duration = duration
199 | angleAnimation.delegate = self
200 | layer.add(angleAnimation, forKey: "angleAnimation")
201 | }
202 |
203 |
204 | /**
205 | 线的第一步动画,线长从长变短
206 | */
207 | private func lineAnimationOne() {
208 | let lineAnimationOne = CABasicAnimation.init(keyPath: "strokeEnd")
209 | lineAnimationOne.beginTime = CACurrentMediaTime()
210 | lineAnimationOne.duration = duration/2
211 | lineAnimationOne.fillMode = CAMediaTimingFillMode.forwards
212 | lineAnimationOne.isRemovedOnCompletion = false
213 | lineAnimationOne.fromValue = 1
214 | lineAnimationOne.toValue = 0
215 | for i in 0...3 {
216 | let lineLayer = lines[i]
217 | lineLayer.add(lineAnimationOne, forKey: "lineAnimationOne")
218 | }
219 | }
220 |
221 | /**
222 | 线的第二步动画,线向中间平移
223 | */
224 | private func lineAnimationTwo() {
225 | for i in 0...3 {
226 | var keypath = "transform.translation.x"
227 | if i%2 == 1 {
228 | keypath = "transform.translation.y"
229 | }
230 | let lineAnimationTwo = CABasicAnimation.init(keyPath: keypath)
231 | lineAnimationTwo.beginTime = CACurrentMediaTime() + duration/2
232 | lineAnimationTwo.duration = duration/4
233 | lineAnimationTwo.fillMode = CAMediaTimingFillMode.forwards
234 | lineAnimationTwo.isRemovedOnCompletion = false
235 | lineAnimationTwo.autoreverses = true
236 | lineAnimationTwo.fromValue = 0
237 | if i < 2 {
238 | lineAnimationTwo.toValue = lineLength/4
239 | }else {
240 | lineAnimationTwo.toValue = -lineLength/4
241 | }
242 | let lineLayer = lines[i]
243 | lineLayer.add(lineAnimationTwo, forKey: "lineAnimationTwo")
244 | }
245 |
246 | //三角形两边的比例
247 | let scale = (lineLength - 2*margin)/(lineLength - lineWidth)
248 | for i in 0...3 {
249 | var keypath = "transform.translation.y"
250 | if i%2 == 1 {
251 | keypath = "transform.translation.x"
252 | }
253 | let lineAnimationTwo = CABasicAnimation.init(keyPath: keypath)
254 | lineAnimationTwo.beginTime = CACurrentMediaTime() + duration/2
255 | lineAnimationTwo.duration = duration/4
256 | lineAnimationTwo.fillMode = CAMediaTimingFillMode.forwards
257 | lineAnimationTwo.isRemovedOnCompletion = false
258 | lineAnimationTwo.autoreverses = true
259 | lineAnimationTwo.fromValue = 0
260 | if i == 0 || i == 3 {
261 | lineAnimationTwo.toValue = lineLength/4 * scale
262 | }else {
263 | lineAnimationTwo.toValue = -lineLength/4 * scale
264 | }
265 | let lineLayer = lines[i]
266 | lineLayer.add(lineAnimationTwo, forKey: "lineAnimationThree")
267 | }
268 | }
269 |
270 | /**
271 | 线的第三步动画,线由短变长
272 | */
273 | private func lineAnimationThree() {
274 | //线移动的动画
275 | let lineAnimationFour = CABasicAnimation.init(keyPath: "strokeEnd")
276 | lineAnimationFour.beginTime = CACurrentMediaTime() + duration
277 | lineAnimationFour.duration = duration/4
278 | lineAnimationFour.fillMode = CAMediaTimingFillMode.forwards
279 | lineAnimationFour.isRemovedOnCompletion = false
280 | lineAnimationFour.fromValue = 0
281 | lineAnimationFour.toValue = 1
282 | for i in 0...3 {
283 | if i == 3 {
284 | lineAnimationFour.delegate = self
285 | }
286 | let lineLayer = lines[i]
287 | lineLayer.add(lineAnimationFour, forKey: "lineAnimationFour")
288 | }
289 | }
290 |
291 | //MARK: Private Methods
292 | private func point(_ x:CGFloat , y:CGFloat) -> CGPoint {
293 | return CGPoint(x: x, y: y)
294 | }
295 |
296 | private func angle(_ angle: Double) -> CGFloat {
297 | return CGFloat(angle * (Double.pi/180))
298 | }
299 |
300 | private func config() {
301 | layoutIfNeeded()
302 | lineLength = max(frame.width, frame.height)
303 | lineWidth = lineLength/6.0
304 | margin = lineLength/4.5 + lineWidth/2
305 | drawLineShapeLayer()
306 | //调整角度
307 | transform = CGAffineTransform.identity.rotated(by: angle(-30))
308 | }
309 | }
310 |
311 | extension CALayer {
312 | //暂停动画
313 | func pauseAnimation() {
314 | // 将当前时间CACurrentMediaTime转换为layer上的时间, 即将parent time转换为localtime
315 | let pauseTime = convertTime(CACurrentMediaTime(), from: nil)
316 | // 设置layer的timeOffset, 在继续操作也会使用到
317 | timeOffset = pauseTime
318 | // localtime与parenttime的比例为0, 意味着localtime暂停了
319 | speed = 0
320 | }
321 |
322 | //继续动画
323 | func resumeAnimation() {
324 | let pausedTime = timeOffset
325 | speed = 1
326 | timeOffset = 0
327 | beginTime = 0
328 | // 计算暂停时间
329 | let sincePause = convertTime(CACurrentMediaTime(), from: nil) - pausedTime
330 | // local time相对于parent time时间的beginTime
331 | beginTime = sincePause
332 | }
333 | }
334 |
--------------------------------------------------------------------------------
/CRRefresh/CRRefresh/CRRefreshAnimator.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CRRefreshAnimator.swift
3 | // CRRefresh
4 | //
5 | // **************************************************
6 | // * _____ *
7 | // * __ _ __ ___ \ / *
8 | // * \ \/ \/ / / __\ / / *
9 | // * \ _ / | (__ / / *
10 | // * \/ \/ \___/ / /__ *
11 | // * /_____/ *
12 | // * *
13 | // **************************************************
14 | // Github :https://github.com/imwcl
15 | // HomePage:http://imwcl.com
16 | // CSDN :http://blog.csdn.net/wang631106979
17 | //
18 | // Created by 王崇磊 on 16/9/14.
19 | // Copyright © 2016年 王崇磊. All rights reserved.
20 | //
21 | // @class CRRefreshAnimator
22 | // @abstract 默认的Animator
23 | // @discussion 默认的Animator
24 | //
25 |
26 | import UIKit
27 |
28 | open class CRRefreshAnimator: CRRefreshProtocol {
29 |
30 | open var view: UIView
31 |
32 | open var insets: UIEdgeInsets
33 |
34 | open var trigger: CGFloat = 60.0
35 |
36 | open var execute: CGFloat = 60.0
37 |
38 | open var endDelay: CGFloat = 0
39 |
40 | public var hold: CGFloat = 60
41 |
42 | public init() {
43 | view = UIView()
44 | insets = UIEdgeInsets.zero
45 | }
46 |
47 | open func refreshBegin(view: CRRefreshComponent) {}
48 |
49 | open func refreshWillEnd(view: CRRefreshComponent) {}
50 |
51 | open func refreshEnd(view: CRRefreshComponent, finish: Bool) {}
52 |
53 | open func refresh(view: CRRefreshComponent, progressDidChange progress: CGFloat) {}
54 |
55 | open func refresh(view: CRRefreshComponent, stateDidChange state: CRRefreshState) {}
56 | }
57 |
--------------------------------------------------------------------------------
/CRRefresh/CRRefresh/CRRefreshBundle.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CRRefreshBundle.swift
3 | // CRRefresh
4 | //
5 | // **************************************************
6 | // * _____ *
7 | // * __ _ __ ___ \ / *
8 | // * \ \/ \/ / / __\ / / *
9 | // * \ _ / | (__ / / *
10 | // * \/ \/ \___/ / /__ *
11 | // * /_____/ *
12 | // * *
13 | // **************************************************
14 | // Github :https://github.com/imwcl
15 | // HomePage:http://imwcl.com
16 | // CSDN :http://blog.csdn.net/wang631106979
17 | //
18 | // Created by 王崇磊 on 16/9/14.
19 | // Copyright © 2016年 王崇磊. All rights reserved.
20 | //
21 | // @class CRRefreshBundle
22 | // @abstract 从Bundle中获取数据的类
23 | // @discussion 从Bundle中获取数据的类
24 | //
25 |
26 | import UIKit
27 |
28 | class CRRefreshBundle {
29 |
30 | var crBundle: Bundle
31 |
32 | init(bundle: Bundle) {
33 | crBundle = bundle
34 | }
35 |
36 | @discardableResult
37 | static func bundle(name: String, for aClass: Swift.AnyClass) -> CRRefreshBundle? {
38 | let bundle = Bundle(for: aClass)
39 | if let path = bundle.path(forResource: name, ofType: "bundle") {
40 | if let bundle = Bundle(path: path) {
41 | return CRRefreshBundle(bundle: bundle)
42 | }
43 | }
44 | return nil
45 | }
46 |
47 | func imageFromBundle(_ imageName: String) -> UIImage? {
48 | var imageName = imageName
49 | if UIScreen.main.scale == 2 {
50 | imageName = imageName + "@2x"
51 | }else if UIScreen.main.scale == 3 {
52 | imageName = imageName + "@3x"
53 | }
54 | let bundle = Bundle(path: crBundle.bundlePath + "/images")
55 | if let path = bundle?.path(forResource: imageName, ofType: "png") {
56 | let image = UIImage(contentsOfFile: path)
57 | return image
58 | }
59 | return nil
60 | }
61 |
62 | func localizedString(key: String) -> String {
63 | if let current = Locale.current.languageCode {
64 | var language = ""
65 | switch current {
66 | case "zh":
67 | language = "zh"
68 | default:
69 | language = "en"
70 | }
71 | if let path = crBundle.path(forResource: language, ofType: "lproj") {
72 | if let bundle = Bundle(path: path) {
73 | let value = bundle.localizedString(forKey: key, value: nil, table: nil)
74 | return Bundle.main.localizedString(forKey: key, value: value, table: nil)
75 | }
76 | }
77 | }
78 | return key
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/CRRefresh/CRRefresh/CRRefreshComponent.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CRRefreshComponent.swift
3 | // CRRefresh
4 | //
5 | // **************************************************
6 | // * _____ *
7 | // * __ _ __ ___ \ / *
8 | // * \ \/ \/ / / __\ / / *
9 | // * \ _ / | (__ / / *
10 | // * \/ \/ \___/ / /__ *
11 | // * /_____/ *
12 | // * *
13 | // **************************************************
14 | // Github :https://github.com/imwcl
15 | // HomePage:http://imwcl.com
16 | // CSDN :http://blog.csdn.net/wang631106979
17 | //
18 | // Created by 王崇磊 on 16/9/14.
19 | // Copyright © 2016年 王崇磊. All rights reserved.
20 | //
21 | // @class CRRefreshComponent
22 | // @abstract 刷新控件的基类
23 | // @discussion 刷新控件的基类
24 | //
25 |
26 | import UIKit
27 |
28 | public typealias CRRefreshHandler = (() -> ())
29 |
30 | public enum CRRefreshState {
31 | /// 普通闲置状态
32 | case idle
33 | /// 松开就可以进行刷新的状态
34 | case pulling
35 | /// 正在刷新中的状态
36 | case refreshing
37 | /// 即将刷新的状态
38 | case willRefresh
39 | /// 所有数据加载完毕,没有更多的数据了
40 | case noMoreData
41 | }
42 |
43 | open class CRRefreshComponent: UIView {
44 |
45 | open weak var scrollView: UIScrollView?
46 |
47 | open var scrollViewInsets: UIEdgeInsets = .zero
48 |
49 | open var handler: CRRefreshHandler?
50 |
51 | open var animator: CRRefreshProtocol!
52 |
53 | open var state: CRRefreshState = .idle {
54 | didSet {
55 | if state != oldValue {
56 | DispatchQueue.main.async {
57 | self.animator.refresh(view: self, stateDidChange: self.state)
58 | }
59 | }
60 | }
61 | }
62 |
63 | fileprivate var isObservingScrollView = false
64 |
65 | fileprivate var isIgnoreObserving = false
66 |
67 | fileprivate(set) var isRefreshing = false
68 |
69 | public override init(frame: CGRect) {
70 | super.init(frame: frame)
71 | autoresizingMask = [.flexibleLeftMargin, .flexibleWidth, .flexibleRightMargin]
72 | }
73 |
74 | public convenience init(animator: CRRefreshProtocol = CRRefreshAnimator(), handler: @escaping CRRefreshHandler) {
75 | self.init(frame: .zero)
76 | self.handler = handler
77 | self.animator = animator
78 | }
79 |
80 | public required init?(coder aDecoder: NSCoder) {
81 | fatalError("init(coder:) has not been implemented")
82 | }
83 |
84 | open override func willMove(toSuperview newSuperview: UIView?) {
85 | super.willMove(toSuperview: newSuperview)
86 | // 旧的父控件移除监听
87 | removeObserver()
88 | if let newSuperview = newSuperview as? UIScrollView {
89 | // 记录UIScrollView最开始的contentInset
90 | scrollViewInsets = newSuperview.contentInset
91 | DispatchQueue.main.async { [weak self, newSuperview] in
92 | guard let weakSelf = self else { return }
93 | weakSelf.addObserver(newSuperview)
94 | }
95 | }
96 | }
97 |
98 | open override func didMoveToSuperview() {
99 | super.didMoveToSuperview()
100 | scrollView = superview as? UIScrollView
101 | let view = animator.view
102 | if view.superview == nil {
103 | let inset = animator.insets
104 | addSubview(view)
105 | view.frame = CGRect(x: inset.left,
106 | y: inset.top,
107 | width: bounds.size.width - inset.left - inset.right,
108 | height: bounds.size.height - inset.top - inset.bottom)
109 | view.autoresizingMask = [
110 | .flexibleWidth,
111 | .flexibleTopMargin,
112 | .flexibleHeight,
113 | .flexibleBottomMargin
114 | ]
115 | }
116 | }
117 |
118 | //MARK: Public Methods
119 | public final func beginRefreshing() -> Void {
120 | guard isRefreshing == false else { return }
121 | if self.window != nil {
122 | state = .refreshing
123 | start()
124 | }else {
125 | if state != .refreshing {
126 | state = .willRefresh
127 | // 预防view还没显示出来就调用了beginRefreshing
128 | DispatchQueue.main.async {
129 | self.scrollViewInsets = self.scrollView?.contentInset ?? .zero
130 | if self.state == .willRefresh {
131 | self.state = .refreshing
132 | self.start()
133 | }
134 | }
135 | }
136 | }
137 | }
138 |
139 | public final func endRefreshing() -> Void {
140 | guard isRefreshing else { return }
141 | self.stop()
142 | }
143 |
144 | public func ignoreObserver(_ ignore: Bool = false) {
145 | isIgnoreObserving = ignore
146 | }
147 |
148 | public func start() {
149 | isRefreshing = true
150 | }
151 |
152 | public func stop() {
153 | isRefreshing = false
154 | }
155 |
156 | public func sizeChange(change: [NSKeyValueChangeKey : Any]?) {}
157 |
158 | public func offsetChange(change: [NSKeyValueChangeKey : Any]?) {}
159 | }
160 |
161 |
162 |
163 | //MARK: Observer Methods
164 | extension CRRefreshComponent {
165 |
166 | fileprivate static var context = "CRRefreshContext"
167 | fileprivate static let offsetKeyPath = "contentOffset"
168 | fileprivate static let contentSizeKeyPath = "contentSize"
169 | public static let animationDuration = 0.25
170 |
171 | fileprivate func removeObserver() {
172 | if let scrollView = superview as? UIScrollView, isObservingScrollView {
173 | scrollView.removeObserver(self, forKeyPath: CRRefreshComponent.offsetKeyPath, context: &CRRefreshComponent.context)
174 | scrollView.removeObserver(self, forKeyPath: CRRefreshComponent.contentSizeKeyPath, context: &CRRefreshComponent.context)
175 | isObservingScrollView = false
176 | }
177 | }
178 |
179 | fileprivate func addObserver(_ view: UIView?) {
180 | if let scrollView = view as? UIScrollView, !isObservingScrollView {
181 | scrollView.addObserver(self, forKeyPath: CRRefreshComponent.offsetKeyPath, options: [.initial, .new], context: &CRRefreshComponent.context)
182 | scrollView.addObserver(self, forKeyPath: CRRefreshComponent.contentSizeKeyPath, options: [.initial, .new], context: &CRRefreshComponent.context)
183 | isObservingScrollView = true
184 | }
185 | }
186 |
187 | open override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
188 | if context == &CRRefreshComponent.context {
189 | guard isUserInteractionEnabled == true && isHidden == false else {
190 | return
191 | }
192 | if keyPath == CRRefreshComponent.contentSizeKeyPath {
193 | if isIgnoreObserving == false {
194 | sizeChange(change: change)
195 | }
196 | } else if keyPath == CRRefreshComponent.offsetKeyPath {
197 | if isIgnoreObserving == false {
198 | offsetChange(change: change)
199 | }
200 | }
201 | }
202 | }
203 | }
204 |
--------------------------------------------------------------------------------
/CRRefresh/CRRefresh/CRRefreshExtension.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CRRefreshExtension.swift
3 | // CRRefresh
4 | //
5 | // **************************************************
6 | // * _____ *
7 | // * __ _ __ ___ \ / *
8 | // * \ \/ \/ / / __\ / / *
9 | // * \ _ / | (__ / / *
10 | // * \/ \/ \___/ / /__ *
11 | // * /_____/ *
12 | // * *
13 | // **************************************************
14 | // Github :https://github.com/imwcl
15 | // HomePage:http://imwcl.com
16 | // CSDN :http://blog.csdn.net/wang631106979
17 | //
18 | // Created by 王崇磊 on 16/9/14.
19 | // Copyright © 2016年 王崇磊. All rights reserved.
20 | //
21 | // @class CRRefreshExtension
22 | // @abstract CRRefreshExtension
23 | // @discussion CRRefreshExtension
24 | //
25 |
26 | import UIKit
27 |
28 | private var kCRRefreshHeaderKey = "kCRRefreshHeaderKey"
29 | private var kCRRefreshFooterKey = "kCRRefreshFooterKey"
30 |
31 | public typealias CRRefreshView = UIScrollView
32 |
33 | extension CRRefreshView {
34 | public var cr: CRRefreshDSL {
35 | return CRRefreshDSL(scroll: self)
36 | }
37 | }
38 |
39 | public struct CRRefreshDSL: CRRefreshViewProtocol {
40 |
41 | public var scroll: CRRefreshView
42 |
43 | internal init(scroll: CRRefreshView) {
44 | self.scroll = scroll
45 | }
46 | /// 添加上拉刷新控件
47 | @discardableResult
48 | public func addHeadRefresh(animator: CRRefreshProtocol = NormalHeaderAnimator(), handler: @escaping CRRefreshHandler) -> CRRefreshHeaderView {
49 | return CRRefreshMake.addHeadRefreshTo(refresh: scroll, animator: animator, handler: handler)
50 | }
51 |
52 | public func beginHeaderRefresh() {
53 | header?.beginRefreshing()
54 | }
55 |
56 | public func endHeaderRefresh() {
57 | header?.endRefreshing()
58 | }
59 |
60 | public func removeHeader() {
61 | var headRefresh = CRRefreshMake(scroll: scroll)
62 | headRefresh.removeHeader()
63 | }
64 |
65 | /// 添加下拉加载控件
66 | @discardableResult
67 | public func addFootRefresh(animator: CRRefreshProtocol = NormalFooterAnimator(), handler: @escaping CRRefreshHandler) -> CRRefreshFooterView {
68 | return CRRefreshMake.addFootRefreshTo(refresh: scroll, animator: animator, handler: handler)
69 | }
70 |
71 | public func noticeNoMoreData() {
72 | footer?.endRefreshing()
73 | footer?.noticeNoMoreData()
74 | }
75 |
76 | public func resetNoMore() {
77 | footer?.resetNoMoreData()
78 | }
79 |
80 | public func endLoadingMore() {
81 | footer?.endRefreshing()
82 | }
83 |
84 | public func removeFooter() {
85 | var footRefresh = CRRefreshMake(scroll: scroll)
86 | footRefresh.removeFooter()
87 | }
88 | }
89 |
90 |
91 | public struct CRRefreshMake: CRRefreshViewProtocol {
92 |
93 | public var scroll: CRRefreshView
94 |
95 | internal init(scroll: CRRefreshView) {
96 | self.scroll = scroll
97 | }
98 |
99 | /// 添加上拉刷新
100 | @discardableResult
101 | internal static func addHeadRefreshTo(refresh: CRRefreshView, animator: CRRefreshProtocol = NormalHeaderAnimator(), handler: @escaping CRRefreshHandler) -> CRRefreshHeaderView {
102 | var make = CRRefreshMake(scroll: refresh)
103 | make.removeHeader()
104 | let header = CRRefreshHeaderView(animator: animator, handler: handler)
105 | let headerH = header.animator.execute
106 | header.frame = .init(x: 0, y: -headerH, width: refresh.bounds.size.width, height: headerH)
107 | refresh.addSubview(header)
108 | make.header = header
109 | return header
110 | }
111 |
112 | public mutating func removeHeader() {
113 | header?.endRefreshing()
114 | header?.removeFromSuperview()
115 | header = nil
116 | }
117 |
118 | /// 添加下拉加载
119 | @discardableResult
120 | internal static func addFootRefreshTo(refresh: CRRefreshView, animator: CRRefreshProtocol = NormalFooterAnimator(), handler: @escaping CRRefreshHandler) -> CRRefreshFooterView {
121 | var make = CRRefreshMake(scroll: refresh)
122 | make.removeFooter()
123 | let footer = CRRefreshFooterView(animator: animator, handler: handler)
124 | let footerH = footer.animator.execute
125 | footer.frame = .init(x: 0, y: refresh.contentSize.height + refresh.contentInset.bottom, width: refresh.bounds.size.width, height: footerH)
126 | refresh.addSubview(footer)
127 | make.footer = footer
128 | return footer
129 | }
130 |
131 | public mutating func removeFooter() {
132 | footer?.endRefreshing()
133 | footer?.removeFromSuperview()
134 | footer = nil
135 | }
136 | }
137 |
138 | public protocol CRRefreshViewProtocol {
139 | var scroll: CRRefreshView {set get}
140 | /// 头部控件
141 | var header: CRRefreshHeaderView? {set get}
142 | /// 头部控件
143 | var footer: CRRefreshFooterView? {set get}
144 | }
145 |
146 | extension CRRefreshViewProtocol {
147 |
148 | public var header: CRRefreshHeaderView? {
149 | get {
150 | return (objc_getAssociatedObject(scroll, &kCRRefreshHeaderKey) as? CRRefreshHeaderView)
151 | }
152 | set {
153 | objc_setAssociatedObject(scroll, &kCRRefreshHeaderKey, newValue, .OBJC_ASSOCIATION_RETAIN)
154 | }
155 | }
156 |
157 | public var footer: CRRefreshFooterView? {
158 | get {
159 | return (objc_getAssociatedObject(scroll, &kCRRefreshFooterKey) as? CRRefreshFooterView)
160 | }
161 | set {
162 | objc_setAssociatedObject(scroll, &kCRRefreshFooterKey, newValue, .OBJC_ASSOCIATION_RETAIN)
163 | }
164 | }
165 | }
166 |
--------------------------------------------------------------------------------
/CRRefresh/CRRefresh/CRRefreshFooterView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CRRefreshFooterView.swift
3 | // CRRefresh
4 | //
5 | // **************************************************
6 | // * _____ *
7 | // * __ _ __ ___ \ / *
8 | // * \ \/ \/ / / __\ / / *
9 | // * \ _ / | (__ / / *
10 | // * \/ \/ \___/ / /__ *
11 | // * /_____/ *
12 | // * *
13 | // **************************************************
14 | // Github :https://github.com/imwcl
15 | // HomePage:http://imwcl.com
16 | // CSDN :http://blog.csdn.net/wang631106979
17 | //
18 | // Created by 王崇磊 on 16/9/14.
19 | // Copyright © 2016年 王崇磊. All rights reserved.
20 | //
21 | // @class CRRefreshFooterView
22 | // @abstract 刷新的尾部控件
23 | // @discussion 刷新的尾部控件
24 | //
25 |
26 | import UIKit
27 |
28 | open class CRRefreshFooterView: CRRefreshComponent {
29 |
30 | open var noMoreData = false {
31 | didSet {
32 | if noMoreData != oldValue {
33 | state = .idle
34 | }
35 | }
36 | }
37 |
38 | open override var isHidden: Bool {
39 | didSet {
40 | if isHidden == true {
41 | scrollView?.contentInset.bottom = scrollViewInsets.bottom
42 | var rect = self.frame
43 | rect.origin.y = scrollView?.contentSize.height ?? 0.0
44 | self.frame = rect
45 | } else {
46 | scrollView?.contentInset.bottom = scrollViewInsets.bottom + animator.execute
47 | var rect = self.frame
48 | rect.origin.y = scrollView?.contentSize.height ?? 0.0
49 | self.frame = rect
50 | }
51 | }
52 | }
53 |
54 | public convenience init(animator: CRRefreshProtocol = NormalFooterAnimator(), handler: @escaping CRRefreshHandler) {
55 | self.init(frame: .zero)
56 | self.handler = handler
57 | self.animator = animator
58 | }
59 |
60 | open override func didMoveToSuperview() {
61 | super.didMoveToSuperview()
62 | DispatchQueue.main.async { [weak self] in
63 | guard let weakSelf = self else { return }
64 | weakSelf.scrollViewInsets = weakSelf.scrollView?.contentInset ?? UIEdgeInsets.zero
65 | weakSelf.scrollView?.contentInset.bottom = weakSelf.scrollViewInsets.bottom + weakSelf.bounds.size.height
66 | var rect = weakSelf.frame
67 | rect.origin.y = weakSelf.scrollView?.contentSize.height ?? 0.0
68 | weakSelf.frame = rect
69 | }
70 | }
71 |
72 | open override func start() {
73 | guard let scrollView = scrollView else { return }
74 | super.start()
75 | animator.refreshBegin(view: self)
76 | let x = scrollView.contentOffset.x
77 | let y = max(0.0, scrollView.contentSize.height - scrollView.bounds.size.height + scrollView.contentInset.bottom)
78 | UIView.animate(withDuration: 0.3, delay: 0.0, options: .curveLinear, animations: {
79 | scrollView.contentOffset = .init(x: x, y: y)
80 | }, completion: { (animated) in
81 | self.handler?()
82 | })
83 | }
84 |
85 | open override func stop() {
86 | guard let scrollView = scrollView else { return }
87 | animator.refreshEnd(view: self, finish: false)
88 | UIView.animate(withDuration: CRRefreshComponent.animationDuration, delay: 0, options: .curveLinear, animations: {
89 | }, completion: { (finished) in
90 | if self.noMoreData == false {
91 | self.state = .idle
92 | }
93 | super.stop()
94 | self.animator.refreshEnd(view: self, finish: true)
95 | })
96 | if scrollView.isDecelerating {
97 | var contentOffset = scrollView.contentOffset
98 | contentOffset.y = min(contentOffset.y, scrollView.contentSize.height - scrollView.frame.size.height)
99 | if contentOffset.y < 0.0 {
100 | contentOffset.y = 0.0
101 | UIView.animate(withDuration: 0.1, animations: {
102 | scrollView.setContentOffset(contentOffset, animated: false)
103 | })
104 | } else {
105 | scrollView.setContentOffset(contentOffset, animated: false)
106 | }
107 | }
108 | }
109 |
110 | open override func sizeChange(change: [NSKeyValueChangeKey : Any]?) {
111 | guard let scrollView = scrollView else { return }
112 | super.sizeChange(change: change)
113 | let targetY = scrollView.contentSize.height + scrollViewInsets.bottom
114 | if self.frame.origin.y != targetY {
115 | var rect = self.frame
116 | rect.origin.y = targetY
117 | self.frame = rect
118 | }
119 | }
120 |
121 | open override func offsetChange(change: [NSKeyValueChangeKey : Any]?) {
122 | guard let scrollView = scrollView else { return }
123 | super.offsetChange(change: change)
124 | guard isRefreshing == false && noMoreData == false && isHidden == false else {
125 | // 正在loading more或者内容为空时不相应变化
126 | return
127 | }
128 |
129 | if scrollView.contentSize.height <= 0.0 || scrollView.contentOffset.y + scrollView.contentInset.top <= 0.0 {
130 | alpha = 0.0
131 | return
132 | } else {
133 | alpha = 1.0
134 | }
135 |
136 | if scrollView.contentSize.height + scrollView.contentInset.top > scrollView.bounds.size.height {
137 | // 内容超过一个屏幕 计算公式,判断是不是在拖在到了底部
138 | if scrollView.contentSize.height - scrollView.contentOffset.y + scrollView.contentInset.bottom <= scrollView.bounds.size.height {
139 | state = .refreshing
140 | beginRefreshing()
141 | }
142 | } else {
143 | //内容没有超过一个屏幕,这时拖拽高度大于1/2footer的高度就表示请求上拉
144 | if scrollView.contentOffset.y + scrollView.contentInset.top >= animator.trigger / 2.0 {
145 | state = .refreshing
146 | beginRefreshing()
147 | }
148 | }
149 | }
150 |
151 | open func noticeNoMoreData() {
152 | noMoreData = true
153 | self.state = .noMoreData
154 | }
155 |
156 | open func resetNoMoreData() {
157 | noMoreData = false
158 | }
159 |
160 | }
161 |
--------------------------------------------------------------------------------
/CRRefresh/CRRefresh/CRRefreshHeaderView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CRRefreshHeaderView.swift
3 | // CRRefresh
4 | //
5 | // **************************************************
6 | // * _____ *
7 | // * __ _ __ ___ \ / *
8 | // * \ \/ \/ / / __\ / / *
9 | // * \ _ / | (__ / / *
10 | // * \/ \/ \___/ / /__ *
11 | // * /_____/ *
12 | // * *
13 | // **************************************************
14 | // Github :https://github.com/imwcl
15 | // HomePage:http://imwcl.com
16 | // CSDN :http://blog.csdn.net/wang631106979
17 | //
18 | // Created by 王崇磊 on 16/9/14.
19 | // Copyright © 2016年 王崇磊. All rights reserved.
20 | //
21 | // @class CRRefreshHeaderView
22 | // @abstract 刷新的头部控件
23 | // @discussion 刷新的头部控件
24 | //
25 |
26 | import UIKit
27 |
28 | open class CRRefreshHeaderView: CRRefreshComponent {
29 |
30 | /// 记录之前的offsetY
31 | fileprivate var previousOffsetY: CGFloat = 0.0
32 | fileprivate var scrollViewBounces: Bool = true
33 | /// 记录结束刷新时需要调整的contentInsetY
34 | fileprivate var insetTDelta: CGFloat = 0.0
35 | /// 记录悬停时需要调整的contentInsetY
36 | fileprivate var holdInsetTDelta: CGFloat = 0.0
37 | /// 是否还在结束中
38 | private var isEnding: Bool = false
39 |
40 | public convenience init(animator: CRRefreshProtocol = NormalHeaderAnimator(), handler: @escaping CRRefreshHandler) {
41 | self.init(frame: .zero)
42 | self.handler = handler
43 | self.animator = animator
44 | }
45 |
46 | open override func didMoveToSuperview() {
47 | super.didMoveToSuperview()
48 | DispatchQueue.main.async { [weak self] in
49 | guard let weakSelf = self else { return }
50 | weakSelf.scrollViewBounces = weakSelf.scrollView?.bounces ?? true
51 |
52 | }
53 | }
54 |
55 | open override func start() {
56 | guard let scrollView = scrollView else { return }
57 | // 动画的时候先忽略监听
58 | ignoreObserver(true)
59 | scrollView.bounces = false
60 | super.start()
61 | // 开始动画
62 | animator.refreshBegin(view: self)
63 | // 调整scrollView的contentInset
64 | var insets = scrollView.contentInset
65 | scrollViewInsets.top = insets.top
66 | insets.top += animator.execute
67 | insetTDelta = -animator.execute
68 | holdInsetTDelta = -(animator.execute - animator.hold)
69 | var point = scrollView.contentOffset;
70 | point.y = -insets.top
71 | UIView.animate(withDuration: CRRefreshComponent.animationDuration, animations: {
72 |
73 | scrollView.contentOffset.y = self.previousOffsetY
74 | scrollView.contentInset = insets
75 | // scrollView.contentOffset.y = -insets.top
76 | scrollView.setContentOffset(point, animated: false);
77 |
78 |
79 | }) { (finished) in
80 | DispatchQueue.main.async {
81 | self.handler?()
82 | self.ignoreObserver(false)
83 | scrollView.bounces = self.scrollViewBounces
84 | }
85 | }
86 | }
87 |
88 | open override func stop() {
89 | guard let scrollView = scrollView else { return }
90 | // 动画的时候先忽略监听
91 | ignoreObserver(true)
92 | animator.refreshWillEnd(view: self)
93 | if self.animator.hold != 0 {
94 | UIView.animate(withDuration: CRRefreshComponent.animationDuration) {
95 | scrollView.contentInset.top += self.holdInsetTDelta
96 | }
97 | }
98 | func beginStop() {
99 | guard isEnding == false, isRefreshing else {
100 | return
101 | }
102 | isEnding = true
103 | // 结束动画
104 | animator.refreshEnd(view: self, finish: false)
105 | // 调整scrollView的contentInset
106 | UIView.animate(withDuration: CRRefreshComponent.animationDuration, animations: {
107 | scrollView.contentInset.top += self.insetTDelta - self.holdInsetTDelta
108 | }) { (finished) in
109 | DispatchQueue.main.async {
110 | self.state = .idle
111 | super.stop()
112 | self.animator.refreshEnd(view: self, finish: true)
113 | self.ignoreObserver(false)
114 | self.isEnding = false
115 | }
116 | }
117 | }
118 | if animator.endDelay > 0 {
119 | if self.isEnding == false {
120 | let delay = DispatchTimeInterval.milliseconds(Int(animator.endDelay * 1000))
121 | DispatchQueue.main.asyncAfter(deadline: .now() + delay, execute: {
122 | beginStop()
123 | })
124 | }
125 | } else {
126 | beginStop()
127 | }
128 | }
129 |
130 | open override func offsetChange(change: [NSKeyValueChangeKey : Any]?) {
131 | guard let scrollView = scrollView else { return }
132 | super.offsetChange(change: change)
133 | // sectionheader停留的解决方案
134 | guard isRefreshing == false else {
135 | if self.window == nil {return}
136 | let top = scrollViewInsets.top
137 | let offsetY = scrollView.contentOffset.y
138 | let height = frame.size.height
139 | var scrollingTop = (-offsetY > top) ? -offsetY : top
140 | scrollingTop = (scrollingTop > height + top) ? (height + top) : scrollingTop
141 | scrollView.contentInset.top = scrollingTop
142 | insetTDelta = scrollViewInsets.top - scrollingTop
143 | return
144 | }
145 |
146 | // 算出Progress
147 | var isRecordingProgress = false
148 | defer {
149 | if isRecordingProgress == true {
150 | let percent = -(previousOffsetY + scrollViewInsets.top) / animator.trigger
151 | animator.refresh(view: self, progressDidChange: percent)
152 | }
153 | }
154 |
155 | let offsets = previousOffsetY + scrollViewInsets.top
156 | if offsets < -animator.trigger {
157 | if isRefreshing == false {
158 | if scrollView.isDragging == false, state == .pulling {
159 | beginRefreshing()
160 | state = .refreshing
161 | } else {
162 | if scrollView.isDragging {
163 | state = .pulling
164 | isRecordingProgress = true
165 | }
166 | }
167 | }
168 | } else if offsets < 0 {
169 | if isRefreshing == false {
170 | state = .idle
171 | isRecordingProgress = true
172 | }
173 | }
174 | previousOffsetY = scrollView.contentOffset.y
175 | }
176 | }
177 |
--------------------------------------------------------------------------------
/CRRefresh/CRRefresh/CRRefreshProtocol.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CRRefreshProtocol.swift
3 | // CRRefresh
4 | //
5 | // **************************************************
6 | // * _____ *
7 | // * __ _ __ ___ \ / *
8 | // * \ \/ \/ / / __\ / / *
9 | // * \ _ / | (__ / / *
10 | // * \/ \/ \___/ / /__ *
11 | // * /_____/ *
12 | // * *
13 | // **************************************************
14 | // Github :https://github.com/imwcl
15 | // HomePage:http://imwcl.com
16 | // CSDN :http://blog.csdn.net/wang631106979
17 | //
18 | // Created by 王崇磊 on 16/9/14.
19 | // Copyright © 2016年 王崇磊. All rights reserved.
20 | //
21 | // @class CRRefreshProtocol
22 | // @abstract 自定义animator需要实现的接口
23 | // @discussion 自定义animator需要实现的接口
24 | //
25 |
26 | import UIKit
27 |
28 | public protocol CRRefreshProtocol {
29 | /// 自定义的view
30 | var view: UIView {get}
31 |
32 | /// view的insets
33 | var insets: UIEdgeInsets {set get}
34 |
35 | /// 触发刷新的高度
36 | var trigger: CGFloat {set get}
37 |
38 | /// 动画执行时的高度
39 | var execute: CGFloat {set get}
40 |
41 | /// 动画结束时延迟的时间,单位秒
42 | var endDelay: CGFloat {set get}
43 |
44 | /// 延迟时悬停的高度
45 | var hold: CGFloat {set get}
46 |
47 | /// 开始刷新
48 | mutating func refreshBegin(view: CRRefreshComponent)
49 |
50 | /// 将要开始刷新
51 | mutating func refreshWillEnd(view: CRRefreshComponent)
52 |
53 | /// 结束刷新
54 | mutating func refreshEnd(view: CRRefreshComponent, finish: Bool)
55 |
56 | /// 刷新进度的变化
57 | mutating func refresh(view: CRRefreshComponent, progressDidChange progress: CGFloat)
58 |
59 | /// 刷新状态的变化
60 | mutating func refresh(view: CRRefreshComponent, stateDidChange state: CRRefreshState)
61 | }
62 |
--------------------------------------------------------------------------------
/CRRefresh/CRRefresh/UIColorExtension.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIColorExtension.swift
3 | // HEXColor
4 | //
5 | // Created by R0CKSTAR on 6/13/14.
6 | // Copyright (c) 2014 P.D.Q. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | extension UIColor {
12 | public convenience init(rgb: (r: CGFloat, g: CGFloat, b: CGFloat)) {
13 | self.init(red: rgb.r/255, green: rgb.g/255, blue: rgb.b/255, alpha: 1.0)
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/CRRefresh/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | $(CURRENT_PROJECT_VERSION)
21 | NSPrincipalClass
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/CRRefresh1.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CRAnimation/CRRefresh/336441963819645dc7427352664f114f5a4cc864/CRRefresh1.gif
--------------------------------------------------------------------------------
/CRRefresh2.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CRAnimation/CRRefresh/336441963819645dc7427352664f114f5a4cc864/CRRefresh2.gif
--------------------------------------------------------------------------------
/CRRefresh3.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CRAnimation/CRRefresh/336441963819645dc7427352664f114f5a4cc864/CRRefresh3.gif
--------------------------------------------------------------------------------
/CRRefresh4.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CRAnimation/CRRefresh/336441963819645dc7427352664f114f5a4cc864/CRRefresh4.gif
--------------------------------------------------------------------------------
/Demo/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // Demo
4 | //
5 | // Created by 王崇磊 on 2017/3/13.
6 | // Copyright © 2017年 王崇磊. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | @UIApplicationMain
12 | class AppDelegate: UIResponder, UIApplicationDelegate {
13 |
14 | var window: UIWindow?
15 |
16 |
17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
18 | // Override point for customization after application launch.
19 | window = UIWindow.init(frame: UIScreen.main.bounds)
20 | window?.makeKeyAndVisible()
21 | let VC = ViewController()
22 | let NAV = NavigationViewController(rootViewController: VC)
23 | window?.rootViewController = NAV
24 |
25 | UITableView.appearance().estimatedSectionHeaderHeight = 0
26 | UITableView.appearance().estimatedSectionFooterHeight = 0
27 |
28 | return true
29 | }
30 |
31 | func applicationWillResignActive(_ application: UIApplication) {
32 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
33 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
34 | }
35 |
36 | func applicationDidEnterBackground(_ application: UIApplication) {
37 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
38 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
39 | }
40 |
41 | func applicationWillEnterForeground(_ application: UIApplication) {
42 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
43 | }
44 |
45 | func applicationDidBecomeActive(_ application: UIApplication) {
46 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
47 | }
48 |
49 | func applicationWillTerminate(_ application: UIApplication) {
50 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
51 | }
52 |
53 |
54 | }
55 |
56 |
--------------------------------------------------------------------------------
/Demo/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "size" : "20x20",
5 | "idiom" : "iphone",
6 | "filename" : "Icon-App-20x20@2x.png",
7 | "scale" : "2x"
8 | },
9 | {
10 | "size" : "20x20",
11 | "idiom" : "iphone",
12 | "filename" : "Icon-App-20x20@3x.png",
13 | "scale" : "3x"
14 | },
15 | {
16 | "size" : "29x29",
17 | "idiom" : "iphone",
18 | "filename" : "Icon-App-29x29@1x.png",
19 | "scale" : "1x"
20 | },
21 | {
22 | "size" : "29x29",
23 | "idiom" : "iphone",
24 | "filename" : "Icon-App-29x29@2x.png",
25 | "scale" : "2x"
26 | },
27 | {
28 | "size" : "29x29",
29 | "idiom" : "iphone",
30 | "filename" : "Icon-App-29x29@3x.png",
31 | "scale" : "3x"
32 | },
33 | {
34 | "size" : "40x40",
35 | "idiom" : "iphone",
36 | "filename" : "Icon-App-40x40@2x.png",
37 | "scale" : "2x"
38 | },
39 | {
40 | "size" : "40x40",
41 | "idiom" : "iphone",
42 | "filename" : "Icon-App-40x40@3x.png",
43 | "scale" : "3x"
44 | },
45 | {
46 | "size" : "57x57",
47 | "idiom" : "iphone",
48 | "filename" : "Icon-App-57x57@1x.png",
49 | "scale" : "1x"
50 | },
51 | {
52 | "size" : "57x57",
53 | "idiom" : "iphone",
54 | "filename" : "Icon-App-57x57@2x.png",
55 | "scale" : "2x"
56 | },
57 | {
58 | "size" : "60x60",
59 | "idiom" : "iphone",
60 | "filename" : "Icon-App-60x60@2x.png",
61 | "scale" : "2x"
62 | },
63 | {
64 | "size" : "60x60",
65 | "idiom" : "iphone",
66 | "filename" : "Icon-App-60x60@3x.png",
67 | "scale" : "3x"
68 | },
69 | {
70 | "size" : "20x20",
71 | "idiom" : "ipad",
72 | "filename" : "Icon-ipad-App-20x20@1x.png",
73 | "scale" : "1x"
74 | },
75 | {
76 | "size" : "20x20",
77 | "idiom" : "ipad",
78 | "filename" : "Icon-ipad-App-20x20@2x.png",
79 | "scale" : "2x"
80 | },
81 | {
82 | "size" : "29x29",
83 | "idiom" : "ipad",
84 | "filename" : "Icon-ipad-App-29x29@1x.png",
85 | "scale" : "1x"
86 | },
87 | {
88 | "size" : "29x29",
89 | "idiom" : "ipad",
90 | "filename" : "Icon-ipad-App-29x29@2x.png",
91 | "scale" : "2x"
92 | },
93 | {
94 | "size" : "40x40",
95 | "idiom" : "ipad",
96 | "filename" : "Icon-ipad-App-40x40@1x.png",
97 | "scale" : "1x"
98 | },
99 | {
100 | "size" : "40x40",
101 | "idiom" : "ipad",
102 | "filename" : "Icon-ipad-App-40x40@2x.png",
103 | "scale" : "2x"
104 | },
105 | {
106 | "size" : "50x50",
107 | "idiom" : "ipad",
108 | "filename" : "Icon-ipad-Small-50x50@1x.png",
109 | "scale" : "1x"
110 | },
111 | {
112 | "size" : "50x50",
113 | "idiom" : "ipad",
114 | "filename" : "Icon-ipad-Small-50x50@2x.png",
115 | "scale" : "2x"
116 | },
117 | {
118 | "size" : "72x72",
119 | "idiom" : "ipad",
120 | "filename" : "Icon-ipad-App-72x72@1x.png",
121 | "scale" : "1x"
122 | },
123 | {
124 | "size" : "72x72",
125 | "idiom" : "ipad",
126 | "filename" : "Icon-ipad-App-72x72@2x.png",
127 | "scale" : "2x"
128 | },
129 | {
130 | "size" : "76x76",
131 | "idiom" : "ipad",
132 | "filename" : "Icon-ipad-App-76x76@1x.png",
133 | "scale" : "1x"
134 | },
135 | {
136 | "size" : "76x76",
137 | "idiom" : "ipad",
138 | "filename" : "Icon-ipad-App-76x76@2x.png",
139 | "scale" : "2x"
140 | },
141 | {
142 | "size" : "83.5x83.5",
143 | "idiom" : "ipad",
144 | "filename" : "Icon-ipad-App-83.5x83.5@2x.png",
145 | "scale" : "2x"
146 | },
147 | {
148 | "size" : "1024x1024",
149 | "idiom" : "ios-marketing",
150 | "filename" : "Icon-ios-App-1024x1024@1x.png",
151 | "scale" : "1x"
152 | }
153 | ],
154 | "info" : {
155 | "version" : 1,
156 | "author" : "xcode"
157 | }
158 | }
--------------------------------------------------------------------------------
/Demo/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CRAnimation/CRRefresh/336441963819645dc7427352664f114f5a4cc864/Demo/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png
--------------------------------------------------------------------------------
/Demo/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CRAnimation/CRRefresh/336441963819645dc7427352664f114f5a4cc864/Demo/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png
--------------------------------------------------------------------------------
/Demo/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CRAnimation/CRRefresh/336441963819645dc7427352664f114f5a4cc864/Demo/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png
--------------------------------------------------------------------------------
/Demo/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CRAnimation/CRRefresh/336441963819645dc7427352664f114f5a4cc864/Demo/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png
--------------------------------------------------------------------------------
/Demo/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CRAnimation/CRRefresh/336441963819645dc7427352664f114f5a4cc864/Demo/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png
--------------------------------------------------------------------------------
/Demo/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CRAnimation/CRRefresh/336441963819645dc7427352664f114f5a4cc864/Demo/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png
--------------------------------------------------------------------------------
/Demo/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CRAnimation/CRRefresh/336441963819645dc7427352664f114f5a4cc864/Demo/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png
--------------------------------------------------------------------------------
/Demo/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CRAnimation/CRRefresh/336441963819645dc7427352664f114f5a4cc864/Demo/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png
--------------------------------------------------------------------------------
/Demo/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CRAnimation/CRRefresh/336441963819645dc7427352664f114f5a4cc864/Demo/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png
--------------------------------------------------------------------------------
/Demo/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CRAnimation/CRRefresh/336441963819645dc7427352664f114f5a4cc864/Demo/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png
--------------------------------------------------------------------------------
/Demo/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CRAnimation/CRRefresh/336441963819645dc7427352664f114f5a4cc864/Demo/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png
--------------------------------------------------------------------------------
/Demo/Assets.xcassets/AppIcon.appiconset/Icon-ios-App-1024x1024@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CRAnimation/CRRefresh/336441963819645dc7427352664f114f5a4cc864/Demo/Assets.xcassets/AppIcon.appiconset/Icon-ios-App-1024x1024@1x.png
--------------------------------------------------------------------------------
/Demo/Assets.xcassets/AppIcon.appiconset/Icon-ipad-App-20x20@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CRAnimation/CRRefresh/336441963819645dc7427352664f114f5a4cc864/Demo/Assets.xcassets/AppIcon.appiconset/Icon-ipad-App-20x20@1x.png
--------------------------------------------------------------------------------
/Demo/Assets.xcassets/AppIcon.appiconset/Icon-ipad-App-20x20@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CRAnimation/CRRefresh/336441963819645dc7427352664f114f5a4cc864/Demo/Assets.xcassets/AppIcon.appiconset/Icon-ipad-App-20x20@2x.png
--------------------------------------------------------------------------------
/Demo/Assets.xcassets/AppIcon.appiconset/Icon-ipad-App-29x29@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CRAnimation/CRRefresh/336441963819645dc7427352664f114f5a4cc864/Demo/Assets.xcassets/AppIcon.appiconset/Icon-ipad-App-29x29@1x.png
--------------------------------------------------------------------------------
/Demo/Assets.xcassets/AppIcon.appiconset/Icon-ipad-App-29x29@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CRAnimation/CRRefresh/336441963819645dc7427352664f114f5a4cc864/Demo/Assets.xcassets/AppIcon.appiconset/Icon-ipad-App-29x29@2x.png
--------------------------------------------------------------------------------
/Demo/Assets.xcassets/AppIcon.appiconset/Icon-ipad-App-40x40@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CRAnimation/CRRefresh/336441963819645dc7427352664f114f5a4cc864/Demo/Assets.xcassets/AppIcon.appiconset/Icon-ipad-App-40x40@1x.png
--------------------------------------------------------------------------------
/Demo/Assets.xcassets/AppIcon.appiconset/Icon-ipad-App-40x40@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CRAnimation/CRRefresh/336441963819645dc7427352664f114f5a4cc864/Demo/Assets.xcassets/AppIcon.appiconset/Icon-ipad-App-40x40@2x.png
--------------------------------------------------------------------------------
/Demo/Assets.xcassets/AppIcon.appiconset/Icon-ipad-App-72x72@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CRAnimation/CRRefresh/336441963819645dc7427352664f114f5a4cc864/Demo/Assets.xcassets/AppIcon.appiconset/Icon-ipad-App-72x72@1x.png
--------------------------------------------------------------------------------
/Demo/Assets.xcassets/AppIcon.appiconset/Icon-ipad-App-72x72@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CRAnimation/CRRefresh/336441963819645dc7427352664f114f5a4cc864/Demo/Assets.xcassets/AppIcon.appiconset/Icon-ipad-App-72x72@2x.png
--------------------------------------------------------------------------------
/Demo/Assets.xcassets/AppIcon.appiconset/Icon-ipad-App-76x76@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CRAnimation/CRRefresh/336441963819645dc7427352664f114f5a4cc864/Demo/Assets.xcassets/AppIcon.appiconset/Icon-ipad-App-76x76@1x.png
--------------------------------------------------------------------------------
/Demo/Assets.xcassets/AppIcon.appiconset/Icon-ipad-App-76x76@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CRAnimation/CRRefresh/336441963819645dc7427352664f114f5a4cc864/Demo/Assets.xcassets/AppIcon.appiconset/Icon-ipad-App-76x76@2x.png
--------------------------------------------------------------------------------
/Demo/Assets.xcassets/AppIcon.appiconset/Icon-ipad-App-83.5x83.5@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CRAnimation/CRRefresh/336441963819645dc7427352664f114f5a4cc864/Demo/Assets.xcassets/AppIcon.appiconset/Icon-ipad-App-83.5x83.5@2x.png
--------------------------------------------------------------------------------
/Demo/Assets.xcassets/AppIcon.appiconset/Icon-ipad-Small-50x50@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CRAnimation/CRRefresh/336441963819645dc7427352664f114f5a4cc864/Demo/Assets.xcassets/AppIcon.appiconset/Icon-ipad-Small-50x50@1x.png
--------------------------------------------------------------------------------
/Demo/Assets.xcassets/AppIcon.appiconset/Icon-ipad-Small-50x50@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CRAnimation/CRRefresh/336441963819645dc7427352664f114f5a4cc864/Demo/Assets.xcassets/AppIcon.appiconset/Icon-ipad-Small-50x50@2x.png
--------------------------------------------------------------------------------
/Demo/Assets.xcassets/Image_1.imageset/3(1).png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CRAnimation/CRRefresh/336441963819645dc7427352664f114f5a4cc864/Demo/Assets.xcassets/Image_1.imageset/3(1).png
--------------------------------------------------------------------------------
/Demo/Assets.xcassets/Image_1.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "universal",
9 | "filename" : "3(1).png",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "version" : 1,
19 | "author" : "xcode"
20 | }
21 | }
--------------------------------------------------------------------------------
/Demo/Assets.xcassets/Image_2.imageset/2(1).png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CRAnimation/CRRefresh/336441963819645dc7427352664f114f5a4cc864/Demo/Assets.xcassets/Image_2.imageset/2(1).png
--------------------------------------------------------------------------------
/Demo/Assets.xcassets/Image_2.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "universal",
9 | "filename" : "2(1).png",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "version" : 1,
19 | "author" : "xcode"
20 | }
21 | }
--------------------------------------------------------------------------------
/Demo/Assets.xcassets/Image_3.imageset/1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CRAnimation/CRRefresh/336441963819645dc7427352664f114f5a4cc864/Demo/Assets.xcassets/Image_3.imageset/1.png
--------------------------------------------------------------------------------
/Demo/Assets.xcassets/Image_3.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "universal",
9 | "filename" : "1.png",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "version" : 1,
19 | "author" : "xcode"
20 | }
21 | }
--------------------------------------------------------------------------------
/Demo/Assets.xcassets/nav_back.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "universal",
9 | "filename" : "icon-返回@2x.png",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "filename" : "icon-返回@3x.png",
15 | "scale" : "3x"
16 | }
17 | ],
18 | "info" : {
19 | "version" : 1,
20 | "author" : "xcode"
21 | }
22 | }
--------------------------------------------------------------------------------
/Demo/Assets.xcassets/nav_back.imageset/icon-返回@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CRAnimation/CRRefresh/336441963819645dc7427352664f114f5a4cc864/Demo/Assets.xcassets/nav_back.imageset/icon-返回@2x.png
--------------------------------------------------------------------------------
/Demo/Assets.xcassets/nav_back.imageset/icon-返回@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CRAnimation/CRRefresh/336441963819645dc7427352664f114f5a4cc864/Demo/Assets.xcassets/nav_back.imageset/icon-返回@3x.png
--------------------------------------------------------------------------------
/Demo/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/Demo/BaseViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // BaseViewController.swift
3 | // HotLine
4 | //
5 | // **************************************************
6 | // * _____ *
7 | // * __ _ __ ___ \ / *
8 | // * \ \/ \/ / / __\ / / *
9 | // * \ _ / | (__ / / *
10 | // * \/ \/ \___/ / /__ *
11 | // * /_____/ *
12 | // * *
13 | // **************************************************
14 | // Github :https://github.com/imwcl
15 | // HomePage:https://imwcl.com
16 | // CSDN :http://blog.csdn.net/wang631106979
17 | //
18 | // Created by 王崇磊 on 16/9/14.
19 | // Copyright © 2016年 王崇磊. All rights reserved.
20 | //
21 | // @class BaseViewController
22 | // @abstract 所有VC的基类
23 | // @discussion 所有VC的基类
24 | //
25 |
26 | import UIKit
27 |
28 | let APP_NAV_BG_COLOR = UIColor(rgb: (140, 141, 178))
29 | let APP_NAV_LINE_COLOR = UIColor.clear
30 | let APP_BACK_COLOR = UIColor.white
31 |
32 |
33 | class BaseViewController: UIViewController, UIGestureRecognizerDelegate {
34 |
35 | var isFirstDidAppear: Bool = false
36 | var isViewAppear: Bool = false
37 |
38 | override var preferredStatusBarStyle: UIStatusBarStyle {
39 | return .lightContent
40 | }
41 |
42 | var navTitle: String?{
43 | didSet{
44 | let titleLabel = UILabel.init(frame: CGRect.zero)
45 | titleLabel.font = UIFont.appMediumFontOfSize(18)
46 | titleLabel.textColor = UIColor.white
47 | titleLabel.text = navTitle
48 | titleLabel.sizeToFit()
49 | self.navigationItem.titleView = titleLabel
50 | }
51 | }
52 |
53 | //MARK: Public Methods
54 | /**
55 | 配置NavBar
56 | */
57 | func configNavBar() {
58 | setNavImage(bgColor: APP_NAV_BG_COLOR, shadowColor: APP_NAV_LINE_COLOR)
59 | }
60 |
61 | /**
62 | View的相关配置
63 | */
64 | func configView() {
65 | view.backgroundColor = APP_BACK_COLOR
66 | }
67 |
68 | //左边nav的点击事件
69 | @objc func leftButtonAction(_ button: UIButton) {
70 | _ = navigationController?.popViewController(animated: true)
71 | }
72 | /**
73 | 添加默认返回按钮
74 | */
75 | @discardableResult
76 | func addNavDefaultBackButton() -> UIButton {
77 | let leftBt = UIButton()
78 | let btImage = UIImage.init(named: "nav_back")
79 | leftBt.setImage(btImage, for: UIControl.State())
80 | leftBt.frame.size = (btImage?.size)!
81 | leftBt.addTarget(self, action: #selector(leftButtonAction(_:)), for: .touchUpInside)
82 | self.navigationItem.leftBarButtonItem = UIBarButtonItem.init(customView: leftBt)
83 | return leftBt
84 | }
85 |
86 | /**
87 | 设置navBar的颜色
88 |
89 | - parameter bgColor: 背景色
90 | - parameter shadowColor: 阴影色
91 |
92 | - returns: self
93 | */
94 | func setNavImage(bgColor: UIColor, shadowColor: UIColor) {
95 | let width = UIScreen.main.bounds.width
96 | let scale = UIScreen.main.scale
97 |
98 | UIGraphicsBeginImageContextWithOptions(CGSize.init(width: width, height: 64), false, scale)
99 | var context = UIGraphicsGetCurrentContext()!
100 | CGContext.setFillColor(context)(bgColor.cgColor)
101 | CGContext.addRect(context)(CGRect.init(x: 0, y: 0, width: width, height: 64))
102 | CGContext.drawPath(context)(using: .fill)
103 | let bgImage = UIGraphicsGetImageFromCurrentImageContext()
104 |
105 | UIGraphicsBeginImageContextWithOptions(CGSize.init(width: width, height: 1), false, scale)
106 | context = UIGraphicsGetCurrentContext()!
107 | CGContext.setLineWidth(context)(1)
108 | CGContext.setStrokeColor(context)(shadowColor.cgColor)
109 | CGContext.move(context)(to: CGPoint.zero)
110 | CGContext.addLine(context)(to: CGPoint.init(x: width, y: 0))
111 | CGContext.drawPath(context)(using: .stroke)
112 | let shadowImage = UIGraphicsGetImageFromCurrentImageContext()
113 |
114 | self.navigationController?.navigationBar.setBackgroundImage(bgImage, for: .default)
115 | self.navigationController?.navigationBar.shadowImage = shadowImage
116 | }
117 |
118 | //MARK: Override
119 | override func viewDidLoad() {
120 | super.viewDidLoad()
121 | // Do any additional setup after loading the view.
122 | configNavBar()
123 | configView()
124 | }
125 |
126 | override func viewDidAppear(_ animated: Bool) {
127 | super.viewWillAppear(animated)
128 | if self.isFirstDidAppear == false {
129 | self.isFirstDidAppear = true
130 | }
131 | self.isViewAppear = true
132 | }
133 |
134 | override func viewWillAppear(_ animated: Bool) {
135 | super.viewWillAppear(animated)
136 | setNavImage(bgColor: APP_NAV_BG_COLOR, shadowColor: APP_NAV_LINE_COLOR)
137 | navigationController?.navigationBar.isTranslucent = false
138 | }
139 |
140 | override func viewDidDisappear(_ animated: Bool) {
141 | super.viewDidDisappear(animated)
142 | self.isViewAppear = false
143 | }
144 |
145 | override func didReceiveMemoryWarning() {
146 | super.didReceiveMemoryWarning()
147 | // Dispose of any resources that can be recreated.
148 | }
149 |
150 | }
151 |
152 | //MARK: 字体
153 | let appFontLightName = "PingFangSC-Light"
154 | let appFontRegularName = "PingFangSC-Regular"
155 | let appFontMediumName = "PingFangSC-Medium"
156 | extension UIFont {
157 | /**
158 | 更具系统不同返回light字体
159 | */
160 | class func appLightFontOfSize(_ fontSize:CGFloat) -> UIFont {
161 | if getSystemVersion() >= 9.0 {
162 | return UIFont.init(name: appFontLightName, size: fontSize) ?? UIFont.systemFont(ofSize: fontSize)
163 | }else {
164 | return UIFont.systemFont(ofSize: fontSize)
165 | }
166 | }
167 | /**
168 | 更具系统不同返回Regular字体
169 | */
170 | class func appRegularFontOfSize(_ fontSize:CGFloat) -> UIFont {
171 | if getSystemVersion() >= 9.0 {
172 | return UIFont.init(name: appFontRegularName, size: fontSize) ?? UIFont.systemFont(ofSize: fontSize)
173 | }else {
174 | return UIFont.systemFont(ofSize: fontSize)
175 | }
176 | }
177 | /**
178 | 更具系统不同返回Medium字体
179 | */
180 | class func appMediumFontOfSize(_ fontSize:CGFloat) -> UIFont {
181 | if getSystemVersion() >= 9.0 {
182 | return UIFont.init(name: appFontMediumName, size: fontSize) ?? UIFont.systemFont(ofSize: fontSize)
183 | }else {
184 | return UIFont.boldSystemFont(ofSize: fontSize)
185 | }
186 | }
187 | }
188 |
189 | /**
190 | 返回系统版本
191 | */
192 | func getSystemVersion() -> Double {
193 | let version = UIDevice.current.systemVersion
194 | var systemVersion = ""
195 | var itemIndex = 0
196 | for item in version.components(separatedBy: ".") {
197 | if itemIndex == 0 {
198 | systemVersion = systemVersion + item + "."
199 | }else {
200 | systemVersion = systemVersion + item
201 | }
202 | itemIndex += 1
203 | }
204 | return Double(systemVersion) ?? 0
205 | }
206 |
207 | public extension UIColor {
208 | public convenience init(rgb: (r: CGFloat, g: CGFloat, b: CGFloat)) {
209 | self.init(red: rgb.r/255, green: rgb.g/255, blue: rgb.b/255, alpha: 1.0)
210 | }
211 | }
212 |
--------------------------------------------------------------------------------
/Demo/Cells/NormalCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NormalCell.swift
3 | // CRRefresh
4 | //
5 | // **************************************************
6 | // * _____ *
7 | // * __ _ __ ___ \ / *
8 | // * \ \/ \/ / / __\ / / *
9 | // * \ _ / | (__ / / *
10 | // * \/ \/ \___/ / /__ *
11 | // * /_____/ *
12 | // * *
13 | // **************************************************
14 | // Github :https://github.com/imwcl
15 | // HomePage:http://imwcl.com
16 | // CSDN :http://blog.csdn.net/wang631106979
17 | //
18 | // Created by 王崇磊 on 16/9/14.
19 | // Copyright © 2016年 王崇磊. All rights reserved.
20 | //
21 | // @class NormalCell
22 | // @abstract 首页普通的cell
23 | // @discussion 首页普通的cell
24 |
25 | import UIKit
26 |
27 | class NormalCell: UITableViewCell {
28 |
29 | @IBOutlet weak var iconView: UIImageView!
30 | @IBOutlet weak var titleLabel: UILabel!
31 | @IBOutlet weak var subTitleLabel: UILabel!
32 |
33 | //MARK: Public Methods
34 | func config(_ model: Refresh.Model) {
35 | iconView.image = model.icon
36 | titleLabel.text = model.title
37 | subTitleLabel.text = model.subTitle
38 | }
39 |
40 | //MARK: Override
41 | override func awakeFromNib() {
42 | super.awakeFromNib()
43 | // Initialization code
44 | iconView.layer.cornerRadius = 24.5
45 | }
46 |
47 | override func setSelected(_ selected: Bool, animated: Bool) {
48 | super.setSelected(selected, animated: animated)
49 |
50 | // Configure the view for the selected state
51 | }
52 |
53 | }
54 |
--------------------------------------------------------------------------------
/Demo/Cells/NormalCell.xib:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
34 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
--------------------------------------------------------------------------------
/Demo/Cells/RefreshCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RefreshCell.swift
3 | // CRRefresh
4 | //
5 | // **************************************************
6 | // * _____ *
7 | // * __ _ __ ___ \ / *
8 | // * \ \/ \/ / / __\ / / *
9 | // * \ _ / | (__ / / *
10 | // * \/ \/ \___/ / /__ *
11 | // * /_____/ *
12 | // * *
13 | // **************************************************
14 | // Github :https://github.com/imwcl
15 | // HomePage:http://imwcl.com
16 | // CSDN :http://blog.csdn.net/wang631106979
17 | //
18 | // Created by 王崇磊 on 16/9/14.
19 | // Copyright © 2016年 王崇磊. All rights reserved.
20 | //
21 | // @class RefreshCell
22 | // @abstract 刷新的cell
23 | // @discussion 刷新的cell
24 |
25 | import UIKit
26 |
27 | class RefreshCell: UITableViewCell {
28 |
29 | @IBOutlet weak var oneRight: NSLayoutConstraint!
30 | @IBOutlet weak var twoRight: NSLayoutConstraint!
31 |
32 | //MARK: Public Methods
33 | override func layoutSubviews() {
34 | super.layoutSubviews()
35 | oneRight.constant = CGFloat(arc4random()%100) + 40
36 | twoRight.constant = CGFloat(arc4random()%100) + 40
37 | }
38 |
39 | //MARK: Override
40 | override func awakeFromNib() {
41 | super.awakeFromNib()
42 | // Initialization code
43 | }
44 |
45 | override func setSelected(_ selected: Bool, animated: Bool) {
46 | super.setSelected(selected, animated: animated)
47 |
48 | // Configure the view for the selected state
49 | }
50 |
51 | }
52 |
--------------------------------------------------------------------------------
/Demo/Cells/RefreshCell.xib:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
--------------------------------------------------------------------------------
/Demo/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleDisplayName
8 | CRRefresh
9 | CFBundleExecutable
10 | $(EXECUTABLE_NAME)
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | $(PRODUCT_NAME)
17 | CFBundlePackageType
18 | APPL
19 | CFBundleShortVersionString
20 | 1.0
21 | CFBundleVersion
22 | 1
23 | LSRequiresIPhoneOS
24 |
25 | UILaunchStoryboardName
26 | LaunchScreen
27 | UIRequiredDeviceCapabilities
28 |
29 | armv7
30 |
31 | UISupportedInterfaceOrientations
32 |
33 | UIInterfaceOrientationPortrait
34 | UIInterfaceOrientationLandscapeLeft
35 | UIInterfaceOrientationLandscapeRight
36 |
37 | UISupportedInterfaceOrientations~ipad
38 |
39 | UIInterfaceOrientationPortrait
40 | UIInterfaceOrientationPortraitUpsideDown
41 | UIInterfaceOrientationLandscapeLeft
42 | UIInterfaceOrientationLandscapeRight
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/Demo/NavigationController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NavigationViewController.swift
3 | // HotLine
4 | //
5 | // **************************************************
6 | // * _____ *
7 | // * __ _ __ ___ \ / *
8 | // * \ \/ \/ / / __\ / / *
9 | // * \ _ / | (__ / / *
10 | // * \/ \/ \___/ / /__ *
11 | // * /_____/ *
12 | // * *
13 | // **************************************************
14 | // Github :https://github.com/imwcl
15 | // HomePage:https://imwcl.com
16 | // CSDN :http://blog.csdn.net/wang631106979
17 | //
18 | // Created by 王崇磊 on 16/9/14.
19 | // Copyright © 2016年 王崇磊. All rights reserved.
20 | //
21 | // @class NavigationViewController
22 | // @abstract 所有nav的基类
23 | // @discussion 所有nav的基类
24 | //
25 |
26 | import UIKit
27 |
28 | class NavigationViewController: UINavigationController,
29 | UINavigationControllerDelegate {
30 |
31 | override var preferredStatusBarStyle: UIStatusBarStyle {
32 | return .lightContent
33 | }
34 |
35 | //MARK: Public Methods
36 |
37 |
38 | //MARK: Override
39 | override func viewDidLoad() {
40 | super.viewDidLoad()
41 | // Do any additional setup after loading the view.
42 | navigationBar.isTranslucent = false
43 | delegate = self
44 | }
45 |
46 | override func didReceiveMemoryWarning() {
47 | super.didReceiveMemoryWarning()
48 | // Dispose of any resources that can be recreated.
49 | }
50 |
51 | override func pushViewController(_ viewController: UIViewController, animated: Bool) {
52 | if self.responds(to: #selector(getter: UINavigationController.interactivePopGestureRecognizer)) {
53 | self.interactivePopGestureRecognizer?.isEnabled = false
54 | }
55 | super.pushViewController(viewController, animated: animated)
56 | }
57 |
58 | //MARK: UINavigationControllerDelegate
59 | func navigationController(_ navigationController: UINavigationController, didShow viewController: UIViewController, animated: Bool) {
60 | if navigationController.responds(to: #selector(getter: UINavigationController.interactivePopGestureRecognizer)) {
61 | navigationController.interactivePopGestureRecognizer?.isEnabled = true
62 | }
63 | if navigationController.viewControllers.count == 1 {
64 | navigationController.interactivePopGestureRecognizer?.isEnabled = false
65 | navigationController.interactivePopGestureRecognizer?.delegate = nil
66 | }
67 | }
68 |
69 |
70 | //MARK: Initial Methods
71 | convenience init(rootViewController: UIViewController, navBarColor: UIColor) {
72 | self.init(rootViewController: rootViewController)
73 | setNavImage(bgColor: navBarColor, shadowColor: UIColor.clear)
74 | }
75 |
76 | //MARK: Setter Getter Methods
77 |
78 |
79 | //MARK: Privater Methods
80 | /**
81 | 设置navBar的颜色
82 |
83 | - parameter bgColor: 背景色
84 | - parameter shadowColor: 阴影色
85 |
86 | - returns: self
87 | */
88 | func setNavImage(bgColor: UIColor, shadowColor: UIColor) {
89 | let width = UIScreen.main.bounds.width
90 | let scale = UIScreen.main.scale
91 |
92 | UIGraphicsBeginImageContextWithOptions(CGSize.init(width: width, height: 64), false, scale)
93 | var context = UIGraphicsGetCurrentContext()!
94 | CGContext.setFillColor(context)(bgColor.cgColor)
95 | CGContext.addRect(context)(CGRect.init(x: 0, y: 0, width: width, height: 64))
96 | CGContext.drawPath(context)(using: .fill)
97 | let bgImage = UIGraphicsGetImageFromCurrentImageContext()
98 |
99 | UIGraphicsBeginImageContextWithOptions(CGSize.init(width: width, height: 1), false, scale)
100 | context = UIGraphicsGetCurrentContext()!
101 | CGContext.setLineWidth(context)(1)
102 | CGContext.setStrokeColor(context)(shadowColor.cgColor)
103 | CGContext.move(context)(to: CGPoint.zero)
104 | CGContext.addLine(context)(to: CGPoint.init(x: width, y: 0))
105 | CGContext.drawPath(context)(using: .stroke)
106 | let shadowImage = UIGraphicsGetImageFromCurrentImageContext()
107 |
108 | self.navigationBar.setBackgroundImage(bgImage, for: .default)
109 | self.navigationBar.shadowImage = shadowImage
110 | }
111 |
112 | //MARK: KVO Methods
113 |
114 |
115 | //MARK: Notification Methods
116 |
117 |
118 | //MARK: Target Methods
119 |
120 | /*
121 | // MARK: - Navigation
122 |
123 | // In a storyboard-based application, you will often want to do a little preparation before navigation
124 | override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
125 | // Get the new view controller using segue.destinationViewController.
126 | // Pass the selected object to the new view controller.
127 | }
128 | */
129 |
130 | }
131 |
--------------------------------------------------------------------------------
/Demo/RefreshController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RefreshController.swift
3 | // CRRefresh
4 | //
5 | // **************************************************
6 | // * _____ *
7 | // * __ _ __ ___ \ / *
8 | // * \ \/ \/ / / __\ / / *
9 | // * \ _ / | (__ / / *
10 | // * \/ \/ \___/ / /__ *
11 | // * /_____/ *
12 | // * *
13 | // **************************************************
14 | // Github :https://github.com/imwcl
15 | // HomePage:http://imwcl.com
16 | // CSDN :http://blog.csdn.net/wang631106979
17 | //
18 | // Created by 王崇磊 on 16/9/14.
19 | // Copyright © 2016年 王崇磊. All rights reserved.
20 | //
21 | // @class RefreshController
22 | // @abstract 刷新VC
23 | // @discussion 刷新VC
24 | //
25 |
26 | import UIKit
27 | import CRRefresh
28 |
29 | class RefreshController: BaseViewController {
30 |
31 | var tableView: UITableView = {
32 | let table = UITableView(frame: .zero, style: .grouped)
33 | return table
34 | }()
35 |
36 | var count: Int = 10
37 |
38 | var refresh: Refresh
39 |
40 | init(refresh: Refresh) {
41 | self.refresh = refresh
42 | super.init(nibName: nil, bundle: nil)
43 | }
44 |
45 | required init?(coder aDecoder: NSCoder) {
46 | fatalError("init(coder:) has not been implemented")
47 | }
48 |
49 | //MARK: Public Methods
50 | override func configNavBar() {
51 | super.configNavBar()
52 | addNavDefaultBackButton()
53 | navTitle = refresh.model.title
54 | }
55 |
56 | override func configView() {
57 | super.configView()
58 | tableView.frame = .init(x: 0, y: 0, width: view.frame.width, height: view.frame.height - 64)
59 | tableView.register(UINib.init(nibName: "RefreshCell", bundle: nil), forCellReuseIdentifier: "RefreshCell")
60 | view.addSubview(tableView)
61 | tableView.delegate = self
62 | tableView.dataSource = self
63 | tableView.separatorStyle = .none
64 |
65 | tableView.cr.addHeadRefresh(animator: refresh.header.commont()) { [weak self] in
66 | print("开始刷新")
67 | DispatchQueue.main.asyncAfter(deadline: .now() + 2, execute: {
68 | self?.count = 10
69 | self?.tableView.cr.endHeaderRefresh()
70 | self?.tableView.cr.resetNoMore()
71 | self?.tableView.reloadData()
72 | })
73 | }
74 |
75 | tableView.cr.beginHeaderRefresh()
76 |
77 | tableView.cr.addFootRefresh(animator: refresh.footer.commont()) { [weak self] in
78 | print("开始加载")
79 | DispatchQueue.main.asyncAfter(deadline: .now() + 2, execute: {
80 | self?.count += 10
81 | self?.tableView.cr.endLoadingMore()
82 | self?.tableView.reloadData()
83 | })
84 | }
85 |
86 | tableView.isPagingEnabled = true
87 |
88 | }
89 |
90 | //MARK: Override
91 | override func viewDidLoad() {
92 | super.viewDidLoad()
93 |
94 | // Do any additional setup after loading the view.
95 | }
96 |
97 | override func didReceiveMemoryWarning() {
98 | super.didReceiveMemoryWarning()
99 | // Dispose of any resources that can be recreated.
100 | }
101 |
102 | }
103 |
104 | // MARK: - Table view data source
105 | extension RefreshController: UITableViewDelegate, UITableViewDataSource {
106 | func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
107 | tableView.deselectRow(at: indexPath, animated: true)
108 | }
109 |
110 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
111 | return count
112 | }
113 |
114 | func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
115 | return CGFloat.leastNormalMagnitude
116 | }
117 |
118 | func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
119 | return CGFloat.leastNormalMagnitude
120 | }
121 |
122 | func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
123 | return 130
124 | }
125 |
126 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
127 | let cell = tableView.dequeueReusableCell(withIdentifier: "RefreshCell", for: indexPath) as! RefreshCell
128 | return cell
129 | }
130 | }
131 |
--------------------------------------------------------------------------------
/Demo/ViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewController.swift
3 | // CRRefresh
4 | //
5 | // Created by 王崇磊 on 2017/2/24.
6 | // Copyright © 2017年 王崇磊. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import CRRefresh
11 |
12 | class ViewController: BaseViewController {
13 |
14 | var tableView: UITableView = {
15 | let table = UITableView(frame: .zero, style: .grouped)
16 | return table
17 | }()
18 |
19 | var refreshs: [Refresh] = [
20 | Refresh(model: .init(icon: #imageLiteral(resourceName: "Image_1"), title: "NormalAnimator", subTitle: "普通刷新控件"), header: .nomalHead, footer: .nomalFoot),
21 | Refresh(model: .init(icon: #imageLiteral(resourceName: "Image_2"), title: "SlackLoadingAnimator", subTitle: "SlackLoading的刷新控件"), header: .slackLoading, footer: .slackLoading),
22 | Refresh(model: .init(icon: #imageLiteral(resourceName: "Image_3"), title: "RamotionAnimator", subTitle: "Ramotion的刷新控件"), header: .ramotion, footer: .nomalFoot),
23 | Refresh(model: .init(icon: #imageLiteral(resourceName: "Image_1"), title: "FastAnimator", subTitle: "FastAnimator的刷新控件"), header: .fast, footer: .nomalFoot)
24 | ]
25 |
26 |
27 | override func viewDidLoad() {
28 | super.viewDidLoad()
29 | }
30 |
31 | override func configNavBar() {
32 | super.configNavBar()
33 | navTitle = "CRRefresh"
34 | }
35 |
36 | override func configView() {
37 | super.configView()
38 | tableView.frame = .init(x: 0, y: 0, width: view.frame.width, height: view.frame.height - 64)
39 | tableView.register(UINib.init(nibName: "NormalCell", bundle: nil), forCellReuseIdentifier: "NormalCell")
40 | view.addSubview(tableView)
41 | tableView.delegate = self
42 | tableView.dataSource = self
43 | }
44 |
45 | override func didReceiveMemoryWarning() {
46 | super.didReceiveMemoryWarning()
47 | // Dispose of any resources that can be recreated.
48 | }
49 | }
50 |
51 |
52 | // MARK: - Table view data source
53 | extension ViewController: UITableViewDelegate, UITableViewDataSource {
54 | func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
55 | tableView.deselectRow(at: indexPath, animated: true)
56 | let refresh = refreshs[indexPath.row]
57 | let vc = RefreshController(refresh: refresh)
58 | navigationController?.pushViewController(vc, animated: true)
59 | }
60 |
61 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
62 | return refreshs.count
63 | }
64 |
65 | func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
66 | return CGFloat.leastNormalMagnitude
67 | }
68 |
69 | func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
70 | return 70
71 | }
72 |
73 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
74 | let cell = tableView.dequeueReusableCell(withIdentifier: "NormalCell", for: indexPath) as! NormalCell
75 | let refresh = refreshs[indexPath.row]
76 | cell.config(refresh.model)
77 | return cell
78 | }
79 | }
80 |
81 | struct Refresh {
82 | var model: Model
83 | var header: Style
84 | var footer: Style
85 | struct Model {
86 | var icon: UIImage
87 | var title: String
88 | var subTitle: String
89 | }
90 |
91 | enum Style {
92 | // 普通刷新类
93 | case nomalHead
94 | case nomalFoot
95 | // slackLoading刷新控件
96 | case slackLoading
97 | // ramotion动画
98 | case ramotion
99 | // fast动画
100 | case fast
101 |
102 | func commont() -> CRRefreshProtocol {
103 | switch self {
104 | case .nomalHead:
105 | return NormalHeaderAnimator()
106 | case .nomalFoot:
107 | return NormalFooterAnimator()
108 | case .slackLoading:
109 | return SlackLoadingAnimator()
110 | case .ramotion:
111 | return RamotionAnimator()
112 | case .fast:
113 | return FastAnimator()
114 | }
115 | }
116 | }
117 | }
118 |
119 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 W_C__L
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](http://cocoapods.org/pods/CRRefresh)
2 | [](http://cocoapods.org/pods/CRRefresh)
3 | [](http://cocoapods.org/pods/CRRefresh)
4 | [](https://www.apple.com/nl/ios/)
5 | 
6 |
7 | 
8 |
9 | **CRRefresh** an easy way to use pull-to-refresh, If you want to customize its UI style, you just need conform the specified protocol. We will not regularly updated some nice dynamic effect, at the same time also welcome to write to me ~
10 |
11 | [中文介绍](README_CN.md) / [博客介绍](http://blog.csdn.net/wang631106979/article/details/62888435)
12 |
13 | ## Screenshots
14 |
15 | |  |  |  |
16 | | :-----------------: | :--------------------: | :-----------------: |
17 | | `NormalAnimator` | `SlackLoadingAnimator` | `RamotionAnimator` |
18 | |  | | |
19 | | `FastAnimator` | | |
20 |
21 | ## Requirements
22 |
23 | - Xcode 8 or later
24 | - iOS 8.0 or later
25 | - ARC
26 | - Swift 3.0 or later
27 |
28 | ## Features
29 |
30 | - Support `UIScrollView` and its subclasses `UICollectionView` `UITableView` `UITextView`
31 | - Pull-Down to refresh and Pull-Up to load more
32 | - Support customize your own style(s)
33 |
34 | ## Installation
35 |
36 | ### CocoaPods
37 |
38 | CocoaPods is the recommended way to add CRRefresh to your project.
39 |
40 | Add a pod entry for CRRefresh to your Podfile.
41 |
42 | ```ruby
43 | pod 'CRRefresh'
44 | ```
45 |
46 | Second, install CRRefresh into your project:
47 |
48 | ```ruby
49 | pod install
50 | ```
51 | ### Carthage
52 |
53 | [Carthage](https://github.com/Carthage/Carthage) is a decentralized dependency manager that builds your dependencies and provides you with binary frameworks. To integrate `CRRefresh` into your Xcode project using Carthage, specify it in your `Cartfile`:
54 |
55 | ```
56 | github "CRAnimation/CRRefresh"
57 | ```
58 |
59 | Run `carthage update` to build the framework and drag the built `CRRefresh.framework` (in Carthage/Build/iOS folder) into your Xcode project (Linked Frameworks and Libraries in `Targets`).
60 |
61 | ### Manually
62 |
63 | 1. Download the latest code version .
64 | 2. Open your project in Xcode,drag the `CRRefresh` folder into your project. Make sure to select Copy items when asked if you extracted the code archive outside of your project.
65 | 3. You need it with `import CRRefresh `.
66 |
67 | ## Usage
68 |
69 | 
70 |
71 | **Add `CRRefresh` to your project**
72 |
73 | ```swift
74 | import CRRefresh
75 | ```
76 |
77 | **Add pull-to-refresh**
78 |
79 | ```swift
80 | /// animator: your customize animator, default is NormalHeaderAnimator
81 | tableView.cr.addHeadRefresh(animator: NormalHeaderAnimator()) { [weak self] in
82 | /// start refresh
83 | /// Do anything you want...
84 | DispatchQueue.main.asyncAfter(deadline: .now() + 2, execute: {
85 | /// Stop refresh when your job finished, it will reset refresh footer if completion is true
86 | self?.tableView.cr.endHeaderRefresh()
87 | })
88 | }
89 | /// manual refresh
90 | tableView.cr.beginHeaderRefresh()
91 | ```
92 |
93 | **Add infinite-scrolling**
94 |
95 | ```swift
96 | /// animator: your customize animator, default is NormalFootAnimator
97 | tableView.cr.addFootRefresh(animator: NormalFootAnimator()) { [weak self] in
98 | /// start refresh
99 | /// Do anything you want...
100 | DispatchQueue.main.asyncAfter(deadline: .now() + 2, execute: {
101 | /// If common end
102 | self?.tableView.cr.endLoadingMore()
103 | /// If no more data
104 | self?.tableView.cr.noticeNoMoreData()
105 | /// Reset no more data
106 | self?.tableView.cr.resetNoMore()
107 | })
108 | }
109 | ```
110 |
111 | ## Customize Style
112 |
113 | Customize refresh need conform the **CRRefreshProtocol** protocol.
114 |
115 | ```swift
116 | public protocol CRRefreshProtocol {
117 | /// Customize view
118 | var view: UIView {get}
119 |
120 | /// View insets
121 | var insets: UIEdgeInsets {set get}
122 |
123 | /// The height of the trigger to refresh
124 | var trigger: CGFloat {set get}
125 |
126 | /// The height of the animation is executed
127 | var execute: CGFloat {set get}
128 |
129 | /// Start refresh
130 | mutating func refreshBegin(view: CRRefreshComponent)
131 |
132 | /// End refresh
133 | mutating func refreshEnd(view: CRRefreshComponent, finish: Bool)
134 |
135 | /// Refresh the progress changes
136 | mutating func refresh(view: CRRefreshComponent, progressDidChange progress: CGFloat)
137 |
138 | /// Refresh the state changes
139 | mutating func refresh(view: CRRefreshComponent, stateDidChange state: CRRefreshState)
140 | }
141 | ```
142 |
143 | ## Remove
144 |
145 | ```swift
146 | tableView.cr.removeFooter()
147 | tableView.cr.removeHeader()
148 | ```
149 |
150 | ## Contribution
151 |
152 | You are welcome to contribute to the project by forking the repo, modifying the code and opening issues or pull requests.
153 |
154 | ## Contacts
155 |
156 | If you wish to contact me
157 |
158 | - Email: wangchonglei93@icloud.com
159 | - Github: https://github.com/imwcl
160 | - QQ: 631106979
161 | - Dynamic effect study group: 547897182
162 |
163 | ## License
164 |
165 | CRRefresh is released under the [MIT license](LICENSE). See LICENSE for details.
166 |
167 |
--------------------------------------------------------------------------------
/README_CN.md:
--------------------------------------------------------------------------------
1 | [](http://cocoapods.org/pods/CRRefresh)
2 | [](http://cocoapods.org/pods/CRRefresh)
3 | [](http://cocoapods.org/pods/CRRefresh)
4 | [](https://www.apple.com/nl/ios/)
5 | 
6 |
7 | 
8 |
9 | **CRRefresh** 是一个简单易用的上拉刷新控件,你可以高度自定义UI样式,我们会不定期更新一些好看的动效,同时也欢迎大家投稿~~
10 |
11 | [英文介绍](README.md) / [博客介绍](http://blog.csdn.net/wang631106979/article/details/62888435)
12 |
13 | ## 效果图
14 |
15 | |  |  |  |
16 | | :-----------------: | :--------------------: | :-----------------: |
17 | | `NormalAnimator` | `SlackLoadingAnimator` | `RamotionAnimator` |
18 | |  | | |
19 | | `FastAnimator` | | |
20 |
21 | ## 支持环境
22 |
23 | - Xcode 8 or later
24 | - iOS 8.0 or later
25 | - ARC
26 | - Swift 3.0 or later
27 |
28 | ## Features
29 |
30 | - 支持`UIScrollView`及其子类`UICollectionView`、`UITableView`、`UIWebView`等
31 | - 支持下拉刷新和上拉加载更多
32 | - 支持下拉刷新和上拉加载更多
33 |
34 | ## 如何安装
35 |
36 | ### CocoaPods
37 |
38 | ```ruby
39 | pod 'CRRefresh'
40 | ```
41 |
42 | ### Carthage
43 |
44 | 使用Carthage安装
45 |
46 | 1. 在`Cartfile`中添加`github "CRAnimation/CRRefresh"`
47 | 2. 执行`carthage update`
48 | 3. 导入`CRRefresh.framework`
49 |
50 | ### 手动安装
51 |
52 | 1. 下载最新版本的代码
53 | 2. 将 Source 内的源文件添加(拖放)到你的工程
54 | 3. 导入 `import CRRefresh `.
55 |
56 | ## 使用
57 |
58 | 
59 |
60 | **添加 `CRRefresh` 到你的工程**
61 |
62 | ```swift
63 | import CRRefresh
64 | ```
65 |
66 | **添加上拉刷新控件**
67 |
68 | ```swift
69 | /// animator: 你的上拉刷新的Animator, 默认是 NormalHeaderAnimator
70 | tableView.cr.addHeadRefresh(animator: NormalHeaderAnimator()) { [weak self] in
71 | /// 开始刷新了
72 | /// 开始刷新的回调
73 | DispatchQueue.main.asyncAfter(deadline: .now() + 2, execute: {
74 | /// 停止刷新
75 | self?.tableView.cr.endHeaderRefresh()
76 | })
77 | }
78 | /// 手动刷新
79 | tableView.cr.beginHeaderRefresh()
80 | ```
81 |
82 | **添加下拉加载控件**
83 |
84 | ```swift
85 | /// animator: 你的下拉加载的Animator, 默认是NormalFootAnimator
86 | tableView.cr.addFootRefresh(animator: NormalFootAnimator()) { [weak self] in
87 | /// 开始下拉加载
88 | /// 回调
89 | DispatchQueue.main.asyncAfter(deadline: .now() + 2, execute: {
90 | /// 结束加载
91 | self?.tableView.cr.endLoadingMore()
92 | /// 没有更多数据
93 | self?.tableView.cr.noticeNoMoreData()
94 | /// 复位
95 | self?.tableView.cr.resetNoMore()
96 | })
97 | }
98 | ```
99 |
100 | ## 自定义样式
101 |
102 | 你只需要实现`CRRefreshProtocol`就可以自定义样式了
103 |
104 | ```swift
105 | public protocol CRRefreshProtocol {
106 | /// 自定义的view
107 | var view: UIView {get}
108 |
109 | /// view的insets
110 | var insets: UIEdgeInsets {set get}
111 |
112 | /// 触发刷新的高度
113 | var trigger: CGFloat {set get}
114 |
115 | /// 动画执行时的高度
116 | var execute: CGFloat {set get}
117 |
118 | /// 开始刷新
119 | mutating func refreshBegin(view: CRRefreshComponent)
120 |
121 | /// 结束刷新
122 | mutating func refreshEnd(view: CRRefreshComponent, finish: Bool)
123 |
124 | /// 刷新进度的变化
125 | mutating func refresh(view: CRRefreshComponent, progressDidChange progress: CGFloat)
126 |
127 | /// 刷新状态的变化
128 | mutating func refresh(view: CRRefreshComponent, stateDidChange state: CRRefreshState)
129 | }
130 | ```
131 |
132 | ## 移除控件
133 |
134 | ```swift
135 | tableView.cr.removeFooter()
136 | tableView.cr.removeHeader()
137 | ```
138 |
139 | ## 贡献你的代码
140 |
141 | 欢迎大家贡献代码,或者投稿好看的动效
142 |
143 | ## 联系
144 |
145 | 如果你想联系我:
146 |
147 | - Email: wangchonglei93@icloud.com
148 | - Github: https://github.com/imwcl
149 | - QQ: 631106979
150 | - 动效学习群:547897182
151 |
152 | ## 许可证
153 |
154 | CRRefresh [MIT license](LICENSE)下发布。有关详细信息,请参阅许可证。
--------------------------------------------------------------------------------