├── art
└── gif
│ ├── header-fly.gif
│ ├── header-drop.gif
│ ├── header-radar.gif
│ ├── header-store.gif
│ ├── header-wave.gif
│ ├── header-circle.gif
│ ├── header-classics.gif
│ ├── header-delivery.gif
│ ├── header-material.gif
│ ├── header-original.gif
│ ├── header-phoenix.gif
│ ├── header-taurus.gif
│ ├── header-game-block.gif
│ └── header-game-tank.gif
├── SmartRefreshDemo
├── Assets.xcassets
│ ├── Contents.json
│ ├── AccentColor.colorset
│ │ └── Contents.json
│ └── AppIcon.appiconset
│ │ └── Contents.json
├── SmartRefreshDemo-Bridging-Header.h
├── ViewController.swift
├── Demo
│ ├── Header
│ │ ├── UITaurusController.swift
│ │ ├── UIClassicsController.swift
│ │ ├── UIDropBoxController.swift
│ │ ├── UIMaterialController.swift
│ │ ├── UIOriginalController.swift
│ │ ├── UIPhoenixController.swift
│ │ ├── UIStoreHouseController.swift
│ │ ├── UIBezierRadarController.swift
│ │ ├── UIBezierCircleController.swift
│ │ ├── UIGameBattleCityController.swift
│ │ ├── UIGameHitBlockController.swift
│ │ ├── UIDeliveryController.swift
│ │ ├── UIWaveSwipeController.swift
│ │ └── UIFlyController.swift
│ ├── Footer
│ │ └── UIClassicsFooterController.swift
│ ├── DemoViewController.swift
│ ├── UIDemoHeaderController.swift
│ └── UIDemoFooterController.swift
├── NavigationViewController.swift
├── AppDelegate.swift
├── Base.lproj
│ └── LaunchScreen.storyboard
├── Info.plist
└── SceneDelegate.swift
├── SmartRefresh.xcodeproj
├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
├── xcuserdata
│ └── scwang.xcuserdatad
│ │ └── xcschemes
│ │ └── xcschememanagement.plist
└── xcshareddata
│ └── xcschemes
│ └── SmartRefreshDemo.xcscheme
├── SmartRefreshControl
├── Fly
│ ├── FlyView.h
│ ├── MountainView.h
│ ├── FlyView.m
│ └── MountainView.m
├── StoreHouse
│ ├── StoreHouseLine.m
│ ├── StoreHouseLine.h
│ └── StoreHousePath.h
├── Game
│ ├── UIRefreshGameStartScene.h
│ ├── UIRefreshGameHeader.h
│ ├── UIRefreshGamePlayingScene.h
│ ├── UIRefreshGameStartScene.m
│ ├── UIRefreshGamePlayingScene.m
│ └── UIRefreshGameHeader.m
├── Header
│ ├── UIRefreshFlyHeader.h
│ ├── UIRefreshTaurusHeader.h
│ ├── UIRefreshDeliveryHeader.h
│ ├── UIRefreshDropBoxHeader.h
│ ├── UIRefreshMaterialHeader.h
│ ├── UIRefreshPhoenixHeader.h
│ ├── UIRefreshWaveSwipeHeader.h
│ ├── UIRefreshBezierCircleHeader.h
│ ├── UIRefreshBezierRadarHeader.h
│ ├── UIRefreshGameHitBlockHeader.h
│ ├── UIRefreshGameBattleCityHeader.h
│ ├── UIRefreshOriginalHeader.h
│ ├── UIRefreshClassicsHeader.h
│ ├── UIRefreshStoreHouseHeader.h
│ ├── UIRefreshGameHitBlockHeader.m
│ ├── UIRefreshClassicsHeader.m
│ ├── UIRefreshFlyHeader.m
│ ├── UIRefreshStoreHouseHeader.m
│ └── UIRefreshGameBattleCityHeader.m
├── Vector
│ ├── Utilities.h
│ ├── PathElement.h
│ ├── Element.h
│ ├── VectorImage.h
│ ├── Element.m
│ ├── ValueAnimator.h
│ ├── Utilities.m
│ ├── PathsParser.h
│ ├── VectorImage.m
│ ├── ValueAnimator.m
│ └── PathElement.m
├── Component
│ ├── UIVectorView.h
│ ├── UIRefreshHeader.h
│ ├── UIRefreshFooter.h
│ ├── UIVectorView.m
│ └── UIRefreshComponent.h
├── Footer
│ ├── UIRefreshClassicsFooter.h
│ └── UIRefreshClassicsFooter.m
├── Info.plist
└── SmartRefreshControl.h
├── LICENSE
├── .gitignore
├── SmartRefreshControl.podspec
└── README.md
/art/gif/header-fly.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scwang90/SmartRefreshControl/HEAD/art/gif/header-fly.gif
--------------------------------------------------------------------------------
/art/gif/header-drop.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scwang90/SmartRefreshControl/HEAD/art/gif/header-drop.gif
--------------------------------------------------------------------------------
/art/gif/header-radar.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scwang90/SmartRefreshControl/HEAD/art/gif/header-radar.gif
--------------------------------------------------------------------------------
/art/gif/header-store.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scwang90/SmartRefreshControl/HEAD/art/gif/header-store.gif
--------------------------------------------------------------------------------
/art/gif/header-wave.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scwang90/SmartRefreshControl/HEAD/art/gif/header-wave.gif
--------------------------------------------------------------------------------
/art/gif/header-circle.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scwang90/SmartRefreshControl/HEAD/art/gif/header-circle.gif
--------------------------------------------------------------------------------
/art/gif/header-classics.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scwang90/SmartRefreshControl/HEAD/art/gif/header-classics.gif
--------------------------------------------------------------------------------
/art/gif/header-delivery.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scwang90/SmartRefreshControl/HEAD/art/gif/header-delivery.gif
--------------------------------------------------------------------------------
/art/gif/header-material.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scwang90/SmartRefreshControl/HEAD/art/gif/header-material.gif
--------------------------------------------------------------------------------
/art/gif/header-original.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scwang90/SmartRefreshControl/HEAD/art/gif/header-original.gif
--------------------------------------------------------------------------------
/art/gif/header-phoenix.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scwang90/SmartRefreshControl/HEAD/art/gif/header-phoenix.gif
--------------------------------------------------------------------------------
/art/gif/header-taurus.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scwang90/SmartRefreshControl/HEAD/art/gif/header-taurus.gif
--------------------------------------------------------------------------------
/art/gif/header-game-block.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scwang90/SmartRefreshControl/HEAD/art/gif/header-game-block.gif
--------------------------------------------------------------------------------
/art/gif/header-game-tank.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scwang90/SmartRefreshControl/HEAD/art/gif/header-game-tank.gif
--------------------------------------------------------------------------------
/SmartRefreshDemo/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/SmartRefresh.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/SmartRefreshDemo/SmartRefreshDemo-Bridging-Header.h:
--------------------------------------------------------------------------------
1 | //
2 | // Use this file to import your target's public headers that you would like to expose to Swift.
3 | //
4 |
5 | #import
6 |
7 |
--------------------------------------------------------------------------------
/SmartRefreshDemo/Assets.xcassets/AccentColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "idiom" : "universal"
5 | }
6 | ],
7 | "info" : {
8 | "author" : "xcode",
9 | "version" : 1
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/SmartRefresh.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/SmartRefreshControl/Fly/FlyView.h:
--------------------------------------------------------------------------------
1 | //
2 | // FlyView.h
3 | // Refresh
4 | //
5 | // Created by SCWANG on 2021/6/19.
6 | // Copyright © 2021 Teeyun. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | NS_ASSUME_NONNULL_BEGIN
12 |
13 | @interface FlyView : UIView
14 |
15 | @property (nonatomic, strong) UIColor *colorAccent;
16 |
17 | @end
18 |
19 | NS_ASSUME_NONNULL_END
20 |
--------------------------------------------------------------------------------
/SmartRefreshDemo/ViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewController.swift
3 | // SmartRefreshDemo
4 | //
5 | // Created by SCWANG on 2021/11/4.
6 | //
7 |
8 | import UIKit
9 |
10 | class ViewController: UIViewController {
11 |
12 | override func viewDidLoad() {
13 | super.viewDidLoad()
14 | // Do any additional setup after loading the view.
15 | }
16 |
17 |
18 | }
19 |
20 |
--------------------------------------------------------------------------------
/SmartRefreshControl/StoreHouse/StoreHouseLine.m:
--------------------------------------------------------------------------------
1 | //
2 | // StoreHouseLine.m
3 | // Refresh
4 | //
5 | // Created by SCWANG on 2021/3/10.
6 | // Copyright © 2021 Teeyun. All rights reserved.
7 | //
8 |
9 | #import "StoreHouseLine.h"
10 |
11 | @implementation StoreHouseLine
12 |
13 | - (instancetype)init
14 | {
15 | self = [super init];
16 | if (self) {
17 | self.transform = CGAffineTransformIdentity;
18 | }
19 | return self;
20 | }
21 |
22 | @end
23 |
--------------------------------------------------------------------------------
/SmartRefreshControl/Game/UIRefreshGameStartScene.h:
--------------------------------------------------------------------------------
1 | //
2 | // UIRefreshGameStartScene.h
3 | // Refresh
4 | //
5 | // Created by SCWANG on 2021/6/8.
6 | // Copyright © 2021 Teeyun. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | NS_ASSUME_NONNULL_BEGIN
12 |
13 | @interface UIRefreshGameStartScene : SKScene
14 |
15 | @property (nonatomic, strong) UIColor *colorAccent;
16 | @property (nonatomic, strong) UIColor *colorPrimary;
17 |
18 | @end
19 |
20 | NS_ASSUME_NONNULL_END
21 |
--------------------------------------------------------------------------------
/SmartRefreshControl/Game/UIRefreshGameHeader.h:
--------------------------------------------------------------------------------
1 | //
2 | // UIRefreshGameHeader.h
3 | // Refresh
4 | //
5 | // Created by SCWANG on 2021/6/6.
6 | // Copyright © 2021 Teeyun. All rights reserved.
7 | //
8 |
9 | #if __has_include()
10 | #import
11 | #else
12 | #import "UIRefreshHeader.h"
13 | #endif
14 |
15 | NS_ASSUME_NONNULL_BEGIN
16 |
17 | @interface UIRefreshGameHeader : UIRefreshHeader
18 |
19 | @end
20 |
21 | NS_ASSUME_NONNULL_END
22 |
--------------------------------------------------------------------------------
/SmartRefreshControl/Header/UIRefreshFlyHeader.h:
--------------------------------------------------------------------------------
1 | //
2 | // UIRefreshFlyHeader.h
3 | // Refresh
4 | //
5 | // Created by SCWANG on 2021/6/13.
6 | // Copyright © 2021 Teeyun. All rights reserved.
7 | //
8 |
9 | #if __has_include()
10 | #import
11 | #else
12 | #import "UIRefreshHeader.h"
13 | #endif
14 |
15 |
16 | NS_ASSUME_NONNULL_BEGIN
17 |
18 | @interface UIRefreshFlyHeader : UIRefreshHeader
19 |
20 | @end
21 |
22 | NS_ASSUME_NONNULL_END
23 |
--------------------------------------------------------------------------------
/SmartRefreshDemo/Demo/Header/UITaurusController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UITaurusController.swift
3 | // Refresh
4 | //
5 | // Created by SCWANG on 2021/6/20.
6 | // Copyright © 2021 Teeyun. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class UITaurusController: UIDemoHeaderController {
12 |
13 | override func initRefreshHeader() -> UIRefreshHeader {
14 | return UIRefreshTaurusHeader.init()
15 | }
16 |
17 | override func viewDidLoad() {
18 | super.viewDidLoad()
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/SmartRefreshControl/Header/UIRefreshTaurusHeader.h:
--------------------------------------------------------------------------------
1 | //
2 | // UIRefreshTaurusHeader.h
3 | // Refresh
4 | //
5 | // Created by Teeyun on 2020/9/10.
6 | // Copyright © 2020 Teeyun. All rights reserved.
7 | //
8 |
9 | #if __has_include()
10 | #import
11 | #else
12 | #import "UIRefreshHeader.h"
13 | #endif
14 |
15 |
16 | NS_ASSUME_NONNULL_BEGIN
17 |
18 | @interface UIRefreshTaurusHeader : UIRefreshHeader
19 |
20 | @end
21 |
22 | NS_ASSUME_NONNULL_END
23 |
--------------------------------------------------------------------------------
/SmartRefreshDemo/Demo/Header/UIClassicsController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIClassicsController.swift
3 | // Refresh
4 | //
5 | // Created by SCWANG on 2021/6/20.
6 | // Copyright © 2021 Teeyun. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class UIClassicsController: UIDemoHeaderController {
12 |
13 | override func initRefreshHeader() -> UIRefreshHeader {
14 | return UIRefreshClassicsHeader.init()
15 | }
16 |
17 | override func viewDidLoad() {
18 | super.viewDidLoad()
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/SmartRefreshDemo/Demo/Header/UIDropBoxController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIDropBoxController.swift
3 | // Refresh
4 | //
5 | // Created by SCWANG on 2021/6/20.
6 | // Copyright © 2021 Teeyun. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class UIDropBoxController: UIDemoHeaderController {
12 |
13 | override func initRefreshHeader() -> UIRefreshHeader {
14 | return UIRefreshDropBoxHeader.init()
15 | }
16 |
17 | override func viewDidLoad() {
18 | super.viewDidLoad()
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/SmartRefreshDemo/Demo/Header/UIMaterialController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIMaterialController.swift
3 | // Refresh
4 | //
5 | // Created by SCWANG on 2021/6/20.
6 | // Copyright © 2021 Teeyun. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class UIMaterialController: UIDemoHeaderController {
12 |
13 | override func initRefreshHeader() -> UIRefreshHeader {
14 | return UIRefreshMaterialHeader.init()
15 | }
16 |
17 | override func viewDidLoad() {
18 | super.viewDidLoad()
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/SmartRefreshDemo/Demo/Header/UIOriginalController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIOriginalController.swift
3 | // Refresh
4 | //
5 | // Created by SCWANG on 2021/6/20.
6 | // Copyright © 2021 Teeyun. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class UIOriginalController: UIDemoHeaderController {
12 |
13 | override func initRefreshHeader() -> UIRefreshHeader {
14 | return UIRefreshOriginalHeader.init()
15 | }
16 |
17 | override func viewDidLoad() {
18 | super.viewDidLoad()
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/SmartRefreshDemo/Demo/Header/UIPhoenixController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIPhoenixController.swift
3 | // Refresh
4 | //
5 | // Created by SCWANG on 2021/6/20.
6 | // Copyright © 2021 Teeyun. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class UIPhoenixController: UIDemoHeaderController {
12 |
13 | override func initRefreshHeader() -> UIRefreshHeader {
14 | return UIRefreshPhoenixHeader.init()
15 | }
16 |
17 | override func viewDidLoad() {
18 | super.viewDidLoad()
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/SmartRefreshControl/Header/UIRefreshDeliveryHeader.h:
--------------------------------------------------------------------------------
1 | //
2 | // UIRefreshDeliveryHeader.h
3 | // Refresh
4 | //
5 | // Created by Teeyun on 2021/1/22.
6 | // Copyright © 2021 Teeyun. All rights reserved.
7 | //
8 |
9 | #if __has_include()
10 | #import
11 | #else
12 | #import "UIRefreshHeader.h"
13 | #endif
14 |
15 |
16 | NS_ASSUME_NONNULL_BEGIN
17 |
18 | @interface UIRefreshDeliveryHeader : UIRefreshHeader
19 |
20 | @end
21 |
22 | NS_ASSUME_NONNULL_END
23 |
--------------------------------------------------------------------------------
/SmartRefreshControl/Header/UIRefreshDropBoxHeader.h:
--------------------------------------------------------------------------------
1 | //
2 | // UIRefreshDropBoxHeader.h
3 | // Refresh
4 | //
5 | // Created by Teeyun on 2020/9/23.
6 | // Copyright © 2020 Teeyun. All rights reserved.
7 | //
8 |
9 | #if __has_include()
10 | #import
11 | #else
12 | #import "UIRefreshHeader.h"
13 | #endif
14 |
15 |
16 | NS_ASSUME_NONNULL_BEGIN
17 |
18 | @interface UIRefreshDropBoxHeader : UIRefreshHeader
19 |
20 | @end
21 |
22 | NS_ASSUME_NONNULL_END
23 |
--------------------------------------------------------------------------------
/SmartRefreshControl/Header/UIRefreshMaterialHeader.h:
--------------------------------------------------------------------------------
1 | //
2 | // UIRefreshMaterialHeader.h
3 | // Refresh
4 | //
5 | // Created by Teeyun on 2021/1/26.
6 | // Copyright © 2021 Teeyun. All rights reserved.
7 | //
8 |
9 | #if __has_include()
10 | #import
11 | #else
12 | #import "UIRefreshHeader.h"
13 | #endif
14 |
15 |
16 | NS_ASSUME_NONNULL_BEGIN
17 |
18 | @interface UIRefreshMaterialHeader : UIRefreshHeader
19 |
20 | @end
21 |
22 | NS_ASSUME_NONNULL_END
23 |
--------------------------------------------------------------------------------
/SmartRefreshControl/Header/UIRefreshPhoenixHeader.h:
--------------------------------------------------------------------------------
1 | //
2 | // UIRefreshPhoenixHeader.h
3 | // Refresh
4 | //
5 | // Created by Teeyun on 2020/9/15.
6 | // Copyright © 2020 Teeyun. All rights reserved.
7 | //
8 |
9 | #if __has_include()
10 | #import
11 | #else
12 | #import "UIRefreshHeader.h"
13 | #endif
14 |
15 |
16 | NS_ASSUME_NONNULL_BEGIN
17 |
18 | @interface UIRefreshPhoenixHeader : UIRefreshHeader
19 |
20 | @end
21 |
22 | NS_ASSUME_NONNULL_END
23 |
--------------------------------------------------------------------------------
/SmartRefreshControl/Header/UIRefreshWaveSwipeHeader.h:
--------------------------------------------------------------------------------
1 | //
2 | // UIRefreshWaveSwipeHeader.h
3 | // Refresh
4 | //
5 | // Created by Teeyun on 2021/1/28.
6 | // Copyright © 2021 Teeyun. All rights reserved.
7 | //
8 |
9 | #if __has_include()
10 | #import
11 | #else
12 | #import "UIRefreshHeader.h"
13 | #endif
14 |
15 |
16 | NS_ASSUME_NONNULL_BEGIN
17 |
18 | @interface UIRefreshWaveSwipeHeader : UIRefreshHeader
19 |
20 | @end
21 |
22 | NS_ASSUME_NONNULL_END
23 |
--------------------------------------------------------------------------------
/SmartRefreshDemo/Demo/Header/UIStoreHouseController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIStoreHouseController.swift
3 | // Refresh
4 | //
5 | // Created by SCWANG on 2021/6/20.
6 | // Copyright © 2021 Teeyun. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class UIStoreHouseController: UIDemoHeaderController {
12 |
13 | override func initRefreshHeader() -> UIRefreshHeader {
14 | return UIRefreshStoreHouseHeader.init()
15 | }
16 |
17 | override func viewDidLoad() {
18 | super.viewDidLoad()
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/SmartRefreshControl/Header/UIRefreshBezierCircleHeader.h:
--------------------------------------------------------------------------------
1 | //
2 | // UIRefreshBezierCircleHeader.h
3 | // Refresh
4 | //
5 | // Created by Teeyun on 2021/1/26.
6 | // Copyright © 2021 Teeyun. All rights reserved.
7 | //
8 |
9 | #if __has_include()
10 | #import
11 | #else
12 | #import "UIRefreshHeader.h"
13 | #endif
14 |
15 | NS_ASSUME_NONNULL_BEGIN
16 |
17 | @interface UIRefreshBezierCircleHeader : UIRefreshHeader
18 |
19 | @end
20 |
21 | NS_ASSUME_NONNULL_END
22 |
--------------------------------------------------------------------------------
/SmartRefreshControl/Vector/Utilities.h:
--------------------------------------------------------------------------------
1 | //
2 | // Utilities.h
3 | // Refresh
4 | //
5 | // Created by Teeyun on 2020/9/8.
6 | // Copyright © 2020 Teeyun. All rights reserved.
7 | //
8 |
9 | #if __has_include()
10 | #import
11 | #else
12 | #import "Element.h"
13 | #endif
14 |
15 | NS_ASSUME_NONNULL_BEGIN
16 |
17 | @interface Utilities : NSObject
18 |
19 | + (CALayer *) newLayerForElement:(Element*) element withPath:(CGPathRef) path;
20 |
21 | @end
22 |
23 | NS_ASSUME_NONNULL_END
24 |
--------------------------------------------------------------------------------
/SmartRefreshDemo/Demo/Header/UIBezierRadarController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIBezierRadarController.swift
3 | // Refresh
4 | //
5 | // Created by SCWANG on 2021/6/20.
6 | // Copyright © 2021 Teeyun. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class UIBezierRadarController: UIDemoHeaderController {
12 |
13 | override func initRefreshHeader() -> UIRefreshHeader {
14 | return UIRefreshBezierRadarHeader.init()
15 | }
16 |
17 | override func viewDidLoad() {
18 | super.viewDidLoad()
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/SmartRefreshControl/Header/UIRefreshBezierRadarHeader.h:
--------------------------------------------------------------------------------
1 | //
2 | // UIRefreshBezierRadarHeader.h
3 | // Refresh
4 | //
5 | // Created by scwang90 on 2021/1/25.
6 | // Copyright © 2021 Teeyun. All rights reserved.
7 | //
8 |
9 | #if __has_include()
10 | #import
11 | #else
12 | #import "UIRefreshHeader.h"
13 | #endif
14 |
15 |
16 | NS_ASSUME_NONNULL_BEGIN
17 |
18 | @interface UIRefreshBezierRadarHeader : UIRefreshHeader
19 |
20 | @end
21 |
22 | NS_ASSUME_NONNULL_END
23 |
--------------------------------------------------------------------------------
/SmartRefreshDemo/Demo/Footer/UIClassicsFooterController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIClassicsFooterController.swift
3 | // Refresh
4 | //
5 | // Created by SCWANG on 2021/7/5.
6 | // Copyright © 2021 Teeyun. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class UIClassicsFooterController: UIDemoFooterController {
12 |
13 | override func initRefreshFooter() -> UIRefreshFooter {
14 | return UIRefreshClassicsFooter.init()
15 | }
16 |
17 | override func viewDidLoad() {
18 | super.viewDidLoad()
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/SmartRefreshDemo/Demo/Header/UIBezierCircleController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIBezierCircleController.swift
3 | // Refresh
4 | //
5 | // Created by SCWANG on 2021/6/20.
6 | // Copyright © 2021 Teeyun. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class UIBezierCircleController: UIDemoHeaderController {
12 |
13 | override func initRefreshHeader() -> UIRefreshHeader {
14 | return UIRefreshBezierCircleHeader.init()
15 | }
16 |
17 | override func viewDidLoad() {
18 | super.viewDidLoad()
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/SmartRefreshDemo/Demo/Header/UIGameBattleCityController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIGameBattleCityController.swift
3 | // Refresh
4 | //
5 | // Created by SCWANG on 2021/6/20.
6 | // Copyright © 2021 Teeyun. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class UIGameBattleCityController: UIDemoHeaderController {
12 |
13 | override func initRefreshHeader() -> UIRefreshHeader {
14 | return UIRefreshGameBattleCityHeader.init()
15 | }
16 |
17 | override func viewDidLoad() {
18 | super.viewDidLoad()
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/SmartRefreshControl/Header/UIRefreshGameHitBlockHeader.h:
--------------------------------------------------------------------------------
1 | //
2 | // UIRefreshGameHitBlockHeader.h
3 | // Refresh
4 | //
5 | // Created by SCWANG on 2021/6/6.
6 | // Copyright © 2021 Teeyun. All rights reserved.
7 | //
8 |
9 | #if __has_include()
10 | #import
11 | #else
12 | #import "UIRefreshGameHeader.h"
13 | #endif
14 |
15 | NS_ASSUME_NONNULL_BEGIN
16 |
17 | @interface UIRefreshGameHitBlockHeader : UIRefreshGameHeader
18 |
19 | @end
20 |
21 | NS_ASSUME_NONNULL_END
22 |
--------------------------------------------------------------------------------
/SmartRefreshControl/Header/UIRefreshGameBattleCityHeader.h:
--------------------------------------------------------------------------------
1 | //
2 | // UIRefreshGameBattleCityHeader.h
3 | // Refresh
4 | //
5 | // Created by SCWANG on 2021/6/9.
6 | // Copyright © 2021 Teeyun. All rights reserved.
7 | //
8 |
9 | #if __has_include()
10 | #import
11 | #else
12 | #import "UIRefreshGameHeader.h"
13 | #endif
14 |
15 |
16 | NS_ASSUME_NONNULL_BEGIN
17 |
18 | @interface UIRefreshGameBattleCityHeader : UIRefreshGameHeader
19 |
20 | @end
21 |
22 | NS_ASSUME_NONNULL_END
23 |
--------------------------------------------------------------------------------
/SmartRefreshControl/Header/UIRefreshOriginalHeader.h:
--------------------------------------------------------------------------------
1 | //
2 | // UIRefreshOriginalHeader.h
3 | // Refresh
4 | //
5 | // Created by Teeyun on 2021/1/26.
6 | // Copyright © 2021 Teeyun. All rights reserved.
7 | //
8 |
9 | #if __has_include()
10 | #import
11 | #else
12 | #import "UIRefreshHeader.h"
13 | #endif
14 |
15 |
16 | NS_ASSUME_NONNULL_BEGIN
17 |
18 | @interface UIRefreshOriginalHeader : UIRefreshHeader
19 |
20 | @property (nonatomic, assign) BOOL enableAutoRefresh;
21 |
22 | @end
23 |
24 | NS_ASSUME_NONNULL_END
25 |
--------------------------------------------------------------------------------
/SmartRefreshDemo/Demo/Header/UIGameHitBlockController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIGameHitBlockController.swift
3 | // Refresh
4 | //
5 | // Created by SCWANG on 2021/6/20.
6 | // Copyright © 2021 Teeyun. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class UIGameHitBlockController: UIDemoHeaderController {
12 |
13 | override func initRefreshHeader() -> UIRefreshHeader {
14 | return UIRefreshGameHitBlockHeader.init()
15 | }
16 |
17 | override func viewDidLoad() {
18 | super.viewDidLoad()
19 |
20 | self.title = "GameHitBlock"
21 |
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/SmartRefreshControl/Vector/PathElement.h:
--------------------------------------------------------------------------------
1 | //
2 | // PathElement.h
3 | // Refresh
4 | //
5 | // Created by Teeyun on 2020/9/8.
6 | // Copyright © 2020 Teeyun. All rights reserved.
7 | //
8 |
9 | #import
10 | #import
11 |
12 | #if __has_include()
13 | #import
14 | #else
15 | #import "Element.h"
16 | #endif
17 |
18 | NS_ASSUME_NONNULL_BEGIN
19 |
20 | @interface PathElement : Element
21 |
22 | + (instancetype) newWith:(NSString*) data;
23 |
24 | @property (nonatomic, readonly) CGPathRef pathForShape;
25 |
26 | @end
27 |
28 | NS_ASSUME_NONNULL_END
29 |
--------------------------------------------------------------------------------
/SmartRefreshControl/Component/UIVectorView.h:
--------------------------------------------------------------------------------
1 | //
2 | // UIVectorView.h
3 | // Refresh
4 | //
5 | // Created by Teeyun on 2020/9/10.
6 | // Copyright © 2020 Teeyun. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | #if __has_include()
12 | #import
13 | #else
14 | #import "VectorImage.h"
15 | #endif
16 |
17 | NS_ASSUME_NONNULL_BEGIN
18 |
19 | @interface UIVectorView : UIView
20 |
21 | @property (nonatomic, strong, nullable) VectorImage *image;
22 |
23 | - (void)parserPaths:(NSArray*) paths;
24 | - (void)parserColors:(NSArray*) colors;
25 |
26 | @end
27 |
28 | NS_ASSUME_NONNULL_END
29 |
--------------------------------------------------------------------------------
/SmartRefreshControl/StoreHouse/StoreHouseLine.h:
--------------------------------------------------------------------------------
1 | //
2 | // StoreHouseLine.h
3 | // Refresh
4 | //
5 | // Created by SCWANG on 2021/3/10.
6 | // Copyright © 2021 Teeyun. All rights reserved.
7 | //
8 | #import
9 |
10 | NS_ASSUME_NONNULL_BEGIN
11 |
12 | @interface StoreHouseLine : NSObject
13 |
14 | @property (nonatomic, assign) CGPoint start;
15 | @property (nonatomic, assign) CGPoint end;
16 | @property (nonatomic, assign) CGPoint middle;
17 | @property (nonatomic, assign) CGFloat alpha;
18 | @property (nonatomic, assign) CGFloat offset;
19 | @property (nonatomic, assign) NSInteger index;
20 | @property (nonatomic, assign) CGAffineTransform transform;
21 |
22 | @end
23 |
24 | NS_ASSUME_NONNULL_END
25 |
--------------------------------------------------------------------------------
/SmartRefreshControl/Fly/MountainView.h:
--------------------------------------------------------------------------------
1 | //
2 | // MountainView.h
3 | // Refresh
4 | //
5 | // Created by SCWANG on 2021/6/13.
6 | // Copyright © 2021 Teeyun. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | #define SCREEN_WIDTH [[UIScreen mainScreen] bounds].size.width
12 |
13 | #define RATIO_Y(rect) (CGRectGetMaxY(rect) / 120.f)
14 | #define RATIO_X(rect) (CGRectGetMaxX(rect) / 240.f)
15 |
16 | NS_ASSUME_NONNULL_BEGIN
17 |
18 | @interface MountainView : UIView
19 |
20 | @property (nonatomic, strong) UIColor *colorAccent;
21 | @property (nonatomic, strong) UIColor *colorPrimary;
22 |
23 | - (void)onScrollingWithOffset:(CGFloat)offset percent:(CGFloat)percent;
24 |
25 | @end
26 |
27 | NS_ASSUME_NONNULL_END
28 |
--------------------------------------------------------------------------------
/SmartRefreshControl/Game/UIRefreshGamePlayingScene.h:
--------------------------------------------------------------------------------
1 | //
2 | // UIRefreshGamePlayingScene.h
3 | // Refresh
4 | //
5 | // Created by SCWANG on 2021/6/8.
6 | // Copyright © 2021 Teeyun. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | NS_ASSUME_NONNULL_BEGIN
12 |
13 | @interface UIRefreshGamePlayingScene : SKScene
14 |
15 | @property (nonatomic, copy) NSString *title;
16 |
17 | @property (nonatomic, strong) UIColor *colorAccent;
18 | @property (nonatomic, strong) UIColor *colorPrimary;
19 |
20 | - (void) reset;
21 | - (void) start;
22 |
23 | - (void) gameWin;
24 | - (void) gameOver;
25 | - (void) moveHandle: (CGFloat) percent;
26 | - (void) finishWithSuccess: (BOOL) success;
27 |
28 | @end
29 |
30 | NS_ASSUME_NONNULL_END
31 |
--------------------------------------------------------------------------------
/SmartRefreshControl/StoreHouse/StoreHousePath.h:
--------------------------------------------------------------------------------
1 | //
2 | // StoreHousePath.h
3 | // Refresh
4 | //
5 | // Created by SCWANG on 2021/3/10.
6 | // Copyright © 2021 Teeyun. All rights reserved.
7 | //
8 |
9 | #if __has_include()
10 | #import
11 | #else
12 | #import "StoreHouseLine.h"
13 | #endif
14 |
15 | NS_ASSUME_NONNULL_BEGIN
16 |
17 | @interface StoreHousePath : NSObject
18 |
19 | + (NSArray*)linesFromArray:(NSArray*)array;
20 | + (NSArray*)linesFrom:(NSString*) str scale:(CGFloat) scale space:(NSInteger) space;
21 |
22 | + (NSArray*) arrayAkta;
23 | + (NSArray*) arrayStoreHouse;
24 |
25 | @end
26 |
27 | NS_ASSUME_NONNULL_END
28 |
--------------------------------------------------------------------------------
/SmartRefreshControl/Footer/UIRefreshClassicsFooter.h:
--------------------------------------------------------------------------------
1 | //
2 | // UIRefreshClassicsFooter.h
3 | // Refresh
4 | //
5 | // Created by SCWANG on 2021/7/5.
6 | // Copyright © 2021 Teeyun. All rights reserved.
7 | //
8 |
9 | #if __has_include()
10 | #import
11 | #else
12 | #import "UIRefreshFooter.h"
13 | #endif
14 |
15 | NS_ASSUME_NONNULL_BEGIN
16 |
17 | @interface UIRefreshClassicsFooter : UIRefreshFooter
18 |
19 | @property (nonatomic, assign) CGFloat spaceOfLoadingAndText;
20 |
21 | @property (nonatomic, readonly) UILabel *labelTitle;
22 | @property (nonatomic, readonly) UIImageView *viewArrow;
23 | @property (nonatomic, readonly) UIActivityIndicatorView *viewLoading;
24 |
25 | @end
26 |
27 | NS_ASSUME_NONNULL_END
28 |
--------------------------------------------------------------------------------
/SmartRefresh.xcodeproj/xcuserdata/scwang.xcuserdatad/xcschemes/xcschememanagement.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SchemeUserState
6 |
7 | SmartRefreshControl.xcscheme_^#shared#^_
8 |
9 | orderHint
10 | 1
11 |
12 | SmartRefreshDemo.xcscheme_^#shared#^_
13 |
14 | orderHint
15 | 0
16 |
17 |
18 | SuppressBuildableAutocreation
19 |
20 | D6CA504727341939001363AB
21 |
22 | primary
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/SmartRefreshControl/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | $(CURRENT_PROJECT_VERSION)
21 |
22 |
23 |
--------------------------------------------------------------------------------
/SmartRefreshControl/Header/UIRefreshClassicsHeader.h:
--------------------------------------------------------------------------------
1 | //
2 | // UIRefreshClassicsHeader.h
3 | // Teecloud
4 | //
5 | // Created by Teeyun on 2020/8/17.
6 | // Copyright © 2020 SCWANG. All rights reserved.
7 | //
8 |
9 | #if __has_include()
10 | #import
11 | #else
12 | #import "UIRefreshHeader.h"
13 | #endif
14 |
15 |
16 | NS_ASSUME_NONNULL_BEGIN
17 |
18 | @interface UIRefreshClassicsHeader : UIRefreshHeader
19 |
20 | @property (nonatomic, assign) CGFloat spaceOfTitleAndTime;
21 | @property (nonatomic, assign) CGFloat spaceOfLoadingAndText;
22 |
23 | @property (nonatomic, readonly) UILabel *labelTitle;
24 | @property (nonatomic, readonly) UILabel *labelLastTime;
25 | @property (nonatomic, readonly) UIImageView *viewArrow;
26 | @property (nonatomic, readonly) UIActivityIndicatorView *viewLoading;
27 |
28 | @end
29 |
30 | NS_ASSUME_NONNULL_END
31 |
--------------------------------------------------------------------------------
/SmartRefreshControl/Vector/Element.h:
--------------------------------------------------------------------------------
1 | //
2 | // Element.h
3 | // Refresh
4 | //
5 | // Created by Teeyun on 2020/9/8.
6 | // Copyright © 2020 Teeyun. All rights reserved.
7 | //
8 |
9 | //#import
10 | //#import
11 | #import
12 |
13 | NS_ASSUME_NONNULL_BEGIN
14 |
15 | @interface Element : NSObject
16 |
17 | @property (nonatomic, copy) NSString *identifier;
18 | @property (nonatomic, strong) UIColor *stroke;
19 | @property (nonatomic, strong) UIColor *fill;
20 | @property (nonatomic, assign) CGFloat opacity;
21 | @property (nonatomic, assign) CGFloat strokeWidth;
22 | @property (nonatomic, assign) CGFloat strokeOpacity;
23 | @property (nonatomic, assign) CGRect viewport;
24 |
25 | @property (nonatomic, assign) BOOL changed;
26 | @property (nonatomic, strong) CALayer *layer;
27 |
28 | - (CALayer *)newLayer;
29 |
30 | @end
31 |
32 | NS_ASSUME_NONNULL_END
33 |
--------------------------------------------------------------------------------
/SmartRefreshDemo/NavigationViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NavigationViewController.swift
3 | // SmartRefreshDemo
4 | //
5 | // Created by SCWANG on 2021/11/4.
6 | //
7 |
8 | import UIKit
9 |
10 | class NavigationViewController: UINavigationController {
11 |
12 | override func viewDidLoad() {
13 | super.viewDidLoad()
14 |
15 | // Do any additional setup after loading the view.
16 | }
17 |
18 |
19 | override var childForStatusBarStyle: UIViewController? {
20 | return self.topViewController;
21 | }
22 |
23 | override var childForStatusBarHidden: UIViewController? {
24 | return self.topViewController;
25 | }
26 |
27 | /*
28 | // MARK: - Navigation
29 |
30 | // In a storyboard-based application, you will often want to do a little preparation before navigation
31 | override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
32 | // Get the new view controller using segue.destination.
33 | // Pass the selected object to the new view controller.
34 | }
35 | */
36 |
37 | }
38 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 树朾
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 |
--------------------------------------------------------------------------------
/SmartRefreshDemo/Demo/Header/UIDeliveryController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIRefreshDeliveryController.swift
3 | // Refresh
4 | //
5 | // Created by SCWANG on 2021/6/20.
6 | // Copyright © 2021 Teeyun. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class UIDeliveryController: UITableViewController {
12 |
13 | let header = UIRefreshDeliveryHeader.init()
14 |
15 | deinit {
16 | NSLog("deinit-%@", self)
17 | }
18 |
19 | override func viewDidLoad() {
20 | super.viewDidLoad()
21 |
22 | self.title = "Delivery"
23 |
24 | header.attach(self.tableView);
25 | header.beginRefresh();
26 | }
27 |
28 | override func numberOfSections(in tableView: UITableView) -> Int {
29 | return 1;
30 | }
31 |
32 | override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
33 | return 20;
34 | }
35 |
36 | override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
37 | return tableView.dequeueReusableCell(withIdentifier: "item", for: indexPath)
38 | }
39 |
40 | }
41 |
--------------------------------------------------------------------------------
/SmartRefreshControl/Vector/VectorImage.h:
--------------------------------------------------------------------------------
1 | //
2 | // VectorImage.h
3 | // Refresh
4 | //
5 | // Created by Teeyun on 2020/9/10.
6 | // Copyright © 2020 Teeyun. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | #if __has_include()
12 | #import
13 | #else
14 | #import "Element.h"
15 | #endif
16 |
17 | NS_ASSUME_NONNULL_BEGIN
18 |
19 | @interface VectorImage : NSObject
20 |
21 | @property (nonatomic, readonly) NSArray *elements;
22 |
23 | @property (nonatomic, assign) CGSize size;
24 | @property (nonatomic, assign) CGRect frame;
25 | @property (nonatomic, assign) CGRect viewport;
26 | @property (nonatomic, assign) CGFloat opacity;
27 |
28 | - (void)addElement:(Element*) element;
29 |
30 | - (void)parserPaths:(NSArray*) paths;
31 | - (void)parserColors:(NSArray*) colors;
32 |
33 | - (void)scaleToFitInside:(CGSize) maxSize;
34 | - (void)renderInContext:(CGContextRef) context;
35 |
36 | - (void)setGeometricWidth:(CGFloat) width;
37 | - (void)setGeometricHeight:(CGFloat) height;
38 |
39 | - (void)moveTo:(CGPoint) pt;
40 |
41 | @end
42 |
43 | NS_ASSUME_NONNULL_END
44 |
--------------------------------------------------------------------------------
/SmartRefreshDemo/Demo/Header/UIWaveSwipeController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIWaveSwipeController.swift
3 | // Refresh
4 | //
5 | // Created by SCWANG on 2021/6/20.
6 | // Copyright © 2021 Teeyun. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class UIWaveSwipeController: UIDemoHeaderController {
12 |
13 | var navShadowImage: UIImage? = nil;
14 |
15 | override func initRefreshHeader() -> UIRefreshHeader {
16 | return UIRefreshWaveSwipeHeader.init()
17 | }
18 |
19 | override func viewWillAppear(_ animated: Bool) {
20 | super.viewWillAppear(animated);
21 |
22 | self.colorPrimary = UIColor.clear;
23 | self.changeTheme(UIColor.systemBlue, UIColor.white)
24 |
25 | if let nav = self.navigationController?.navigationBar {
26 | self.navShadowImage = nav.shadowImage;
27 | nav.shadowImage = UIImage.init();
28 | }
29 | }
30 | override func viewWillDisappear(_ animated: Bool) {
31 | super.viewWillDisappear(animated);
32 |
33 | if let nav = self.navigationController?.navigationBar {
34 | nav.shadowImage = self.navShadowImage;
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/SmartRefreshControl/Vector/Element.m:
--------------------------------------------------------------------------------
1 | //
2 | // Element.m
3 | // Refresh
4 | //
5 | // Created by Teeyun on 2020/9/8.
6 | // Copyright © 2020 Teeyun. All rights reserved.
7 | //
8 |
9 | #import "Element.h"
10 |
11 | @implementation Element
12 | @synthesize layer = _layer;
13 |
14 | - (instancetype)init
15 | {
16 | self = [super init];
17 | if (self) {
18 | self.strokeWidth = 0;
19 | self.fill = [UIColor whiteColor];
20 | self.opacity = 1;
21 | }
22 | return self;
23 | }
24 |
25 | - (void)setLayer:(CALayer *)layer {
26 | _layer = layer;
27 | _changed = FALSE;
28 | }
29 |
30 | - (void)setStroke:(UIColor *)stroke {
31 | _stroke = stroke;
32 | _changed = TRUE;
33 | }
34 |
35 | - (void)setFill:(UIColor *)fill {
36 | _fill = fill;
37 | _changed = TRUE;
38 | }
39 |
40 | - (void)setOpacity:(CGFloat)opacity {
41 | _opacity = opacity;
42 | _changed = TRUE;
43 | }
44 |
45 | - (void)setStrokeWidth:(CGFloat)strokeWidth {
46 | _strokeWidth = strokeWidth;
47 | _changed = TRUE;
48 | }
49 |
50 | - (CALayer *)layer {
51 | if (_changed) {
52 | return _layer = [self newLayer];
53 | }
54 | return _layer;
55 | }
56 |
57 | - (CALayer *)newLayer {
58 | return nil;
59 | }
60 |
61 | @end
62 |
--------------------------------------------------------------------------------
/SmartRefreshDemo/Demo/Header/UIFlyController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIFlyController.swift
3 | // Refresh
4 | //
5 | // Created by SCWANG on 2021/6/20.
6 | // Copyright © 2021 Teeyun. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class UIFlyController: UIDemoHeaderController {
12 |
13 | var navShadowImage: UIImage? = nil;
14 |
15 | override func initRefreshHeader() -> UIRefreshHeader {
16 | return UIRefreshFlyHeader.init()
17 | }
18 |
19 | override func viewWillAppear(_ animated: Bool) {
20 | super.viewWillAppear(animated);
21 |
22 | let color = UIColor.init(red: 0.2, green: 0.6, blue: 1, alpha: 1)
23 |
24 | self.changeTheme(color, UIColor.white)
25 |
26 | if let nav = self.navigationController?.navigationBar {
27 | self.navShadowImage = nav.shadowImage;
28 | nav.shadowImage = UIImage.init();
29 | }
30 | }
31 | override func viewWillDisappear(_ animated: Bool) {
32 | super.viewWillDisappear(animated);
33 |
34 | if let nav = self.navigationController?.navigationBar {
35 | nav.shadowImage = self.navShadowImage;
36 | }
37 | }
38 |
39 | override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
40 | return 25
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/SmartRefreshControl/Header/UIRefreshStoreHouseHeader.h:
--------------------------------------------------------------------------------
1 | //
2 | // UIRefreshStoreHouseHeader.h
3 | // Refresh
4 | //
5 | // Created by Teeyun on 2021/2/1.
6 | // Copyright © 2021 Teeyun. All rights reserved.
7 | //
8 |
9 | #if __has_include()
10 | #import
11 | #else
12 | #import "UIRefreshHeader.h"
13 | #endif
14 |
15 |
16 | NS_ASSUME_NONNULL_BEGIN
17 |
18 | @interface UIRefreshStoreHouseHeader : UIRefreshHeader
19 |
20 | @property (nonatomic, assign) CGFloat lineWidth;
21 |
22 | @property (nonatomic, assign) BOOL enableReverseFlash;
23 | @property (nonatomic, assign) BOOL enableFadeAnimation;
24 |
25 | //@property (nonatomic, assign) CGFloat loadingAniFlastDuration;
26 | //@property (nonatomic, assign) CGFloat loadingAniAlphaDuration;
27 |
28 | + (instancetype) newWithString:(NSString*) string;
29 | + (instancetype) newWithString:(NSString*) string scale:(CGFloat) scale;
30 | + (instancetype) newWithString:(NSString*) string scale:(CGFloat) scale padding:(CGFloat) padding;
31 |
32 | - (void) initLinesWithString:(NSString*) string;
33 | - (void) initLinesWithString:(NSString*) string scale:(CGFloat) scale;
34 | - (void) initLinesWithString:(NSString*) string scale:(CGFloat) scale padding:(CGFloat) padding;
35 |
36 | - (void) initLinesWithArray:(NSArray*)array;
37 | - (void) initLinesWithArray:(NSArray*)array padding:(CGFloat) padding;
38 |
39 | @end
40 |
41 | NS_ASSUME_NONNULL_END
42 |
--------------------------------------------------------------------------------
/SmartRefreshDemo/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // SmartRefreshDemo
4 | //
5 | // Created by SCWANG on 2021/11/4.
6 | //
7 |
8 | import UIKit
9 |
10 | @main
11 | class AppDelegate: UIResponder, UIApplicationDelegate {
12 |
13 |
14 |
15 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
16 | // Override point for customization after application launch.
17 | return true
18 | }
19 |
20 | // MARK: UISceneSession Lifecycle
21 |
22 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
23 | // Called when a new scene session is being created.
24 | // Use this method to select a configuration to create the new scene with.
25 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
26 | }
27 |
28 | func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) {
29 | // Called when the user discards a scene session.
30 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
31 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return.
32 | }
33 |
34 |
35 | }
36 |
37 |
--------------------------------------------------------------------------------
/SmartRefreshControl/Fly/FlyView.m:
--------------------------------------------------------------------------------
1 | //
2 | // FlyView.m
3 | // Refresh
4 | //
5 | // Created by SCWANG on 2021/6/19.
6 | // Copyright © 2021 Teeyun. All rights reserved.
7 | //
8 |
9 | #import "FlyView.h"
10 | #import "VectorImage.h"
11 |
12 | @interface FlyView ()
13 |
14 | @property (nonatomic, strong) VectorImage *image;
15 |
16 | @end
17 |
18 | @implementation FlyView
19 |
20 | - (instancetype)initWithFrame:(CGRect)frame
21 | {
22 | if (CGRectEqualToRect(CGRectZero, frame)) {
23 | frame = CGRectMake(0, 0, 30, 30);
24 | }
25 | self = [super initWithFrame:frame];
26 | if (self) {
27 | [self setOpaque:FALSE];
28 | [self setImage:[VectorImage new]];
29 | }
30 | return self;
31 | }
32 |
33 | - (void)setImage:(VectorImage *)image {
34 | _image = image;
35 | [image parserColors:@[UIColor.whiteColor]];
36 | [image parserPaths:@[@"M2.01,21L23,12 2.01,3 2,10l15,2 -15,2z"]];
37 | if (self.frame.size.height > 0 && self.frame.size.width > 0) {
38 | [image scaleToFitInside:self.frame.size];
39 | }
40 | }
41 |
42 | - (void)setFrame:(CGRect)frame {
43 | [super setFrame:frame];
44 | if (_image) {
45 | [_image scaleToFitInside:self.frame.size];
46 | }
47 | }
48 |
49 | - (void)setColorAccent:(UIColor *)colorAccent {
50 | _colorAccent = colorAccent;
51 | [self.image parserColors:@[colorAccent]];
52 | }
53 |
54 | // Only override drawRect: if you perform custom drawing.
55 | // An empty implementation adversely affects performance during animation.
56 | - (void)drawRect:(CGRect)rect {
57 | // Drawing code
58 | [self.image renderInContext:UIGraphicsGetCurrentContext()];
59 | }
60 |
61 | @end
62 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Xcode
2 | #
3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
4 |
5 | ## Build generated
6 | build/
7 | DerivedData/
8 |
9 | ## Various settings
10 | *.pbxuser
11 | !default.pbxuser
12 | *.mode1v3
13 | !default.mode1v3
14 | *.mode2v3
15 | !default.mode2v3
16 | *.perspectivev3
17 | !default.perspectivev3
18 | xcuserdata/
19 |
20 | ## Other
21 | *.moved-aside
22 | *.xccheckout
23 | *.xcscmblueprint
24 |
25 | ## Obj-C/Swift specific
26 | *.hmap
27 | *.ipa
28 | *.dSYM.zip
29 | *.dSYM
30 |
31 | # CocoaPods
32 | #
33 | # We recommend against adding the Pods directory to your .gitignore. However
34 | # you should judge for yourself, the pros and cons are mentioned at:
35 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
36 | #
37 | # Pods/
38 |
39 | # Carthage
40 | #
41 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
42 | # Carthage/Checkouts
43 |
44 | Carthage/Build
45 |
46 | # fastlane
47 | #
48 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
49 | # screenshots whenever they are needed.
50 | # For more information about the recommended setup visit:
51 | # https://docs.fastlane.tools/best-practices/source-control/#source-control
52 |
53 | fastlane/report.xml
54 | fastlane/Preview.html
55 | fastlane/screenshots
56 | fastlane/test_output
57 |
58 | # Code Injection
59 | #
60 | # After new code Injection tools there's a generated folder /iOSInjectionProject
61 | # https://github.com/johnno1962/injectionforxcode
62 |
63 | iOSInjectionProject/
64 |
65 | .idea
66 | ._*
67 |
--------------------------------------------------------------------------------
/SmartRefreshDemo/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 |
--------------------------------------------------------------------------------
/SmartRefreshDemo/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "scale" : "2x",
6 | "size" : "20x20"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "scale" : "3x",
11 | "size" : "20x20"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "scale" : "2x",
16 | "size" : "29x29"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "scale" : "3x",
21 | "size" : "29x29"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "scale" : "2x",
26 | "size" : "40x40"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "scale" : "3x",
31 | "size" : "40x40"
32 | },
33 | {
34 | "idiom" : "iphone",
35 | "scale" : "2x",
36 | "size" : "60x60"
37 | },
38 | {
39 | "idiom" : "iphone",
40 | "scale" : "3x",
41 | "size" : "60x60"
42 | },
43 | {
44 | "idiom" : "ipad",
45 | "scale" : "1x",
46 | "size" : "20x20"
47 | },
48 | {
49 | "idiom" : "ipad",
50 | "scale" : "2x",
51 | "size" : "20x20"
52 | },
53 | {
54 | "idiom" : "ipad",
55 | "scale" : "1x",
56 | "size" : "29x29"
57 | },
58 | {
59 | "idiom" : "ipad",
60 | "scale" : "2x",
61 | "size" : "29x29"
62 | },
63 | {
64 | "idiom" : "ipad",
65 | "scale" : "1x",
66 | "size" : "40x40"
67 | },
68 | {
69 | "idiom" : "ipad",
70 | "scale" : "2x",
71 | "size" : "40x40"
72 | },
73 | {
74 | "idiom" : "ipad",
75 | "scale" : "1x",
76 | "size" : "76x76"
77 | },
78 | {
79 | "idiom" : "ipad",
80 | "scale" : "2x",
81 | "size" : "76x76"
82 | },
83 | {
84 | "idiom" : "ipad",
85 | "scale" : "2x",
86 | "size" : "83.5x83.5"
87 | },
88 | {
89 | "idiom" : "ios-marketing",
90 | "scale" : "1x",
91 | "size" : "1024x1024"
92 | }
93 | ],
94 | "info" : {
95 | "author" : "xcode",
96 | "version" : 1
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/SmartRefreshControl/Component/UIRefreshHeader.h:
--------------------------------------------------------------------------------
1 | //
2 | // UIRefreshHeader.h
3 | // Teecloud
4 | //
5 | // Created by Teeyun on 2020/8/17.
6 | // Copyright © 2020 SCWANG. All rights reserve.d
7 | //
8 |
9 | #if __has_include()
10 | #import
11 | #else
12 | #import "UIRefreshComponent.h"
13 | #endif
14 |
15 | NS_ASSUME_NONNULL_BEGIN
16 |
17 | /**
18 | * 刷新状态
19 | */
20 | typedef NS_ENUM(NSUInteger, UIRefreshStatus) {
21 | UIRefreshStatusIdle,
22 | UIRefreshStatusPullToRefresh,
23 | UIRefreshStatusReleaseToRefresh,
24 | UIRefreshStatusWillRefresh, //调用 beginRefresh 时才会触发
25 | UIRefreshStatusReleasing,
26 | UIRefreshStatusRefreshing,
27 | UIRefreshStatusFinish,
28 | };
29 |
30 |
31 | @class UIRefreshHeader;
32 |
33 | typedef void(^RefreshBlock)(UIRefreshHeader *header);
34 |
35 | @interface UIRefreshHeader : UIRefreshComponent
36 |
37 | @property (nonatomic, copy) RefreshBlock refreshBlock;
38 | @property (nonatomic, copy) NSString *keyForLastRefreshTime;
39 | @property (nonatomic, copy) NSString *nameForLastRefreshTime;
40 |
41 | @property (nonatomic, strong) NSDate *lastRefreshTime;
42 | @property (nonatomic, strong) NSDateFormatter *lastRefreshTimeFormatter;
43 |
44 | @property (nonatomic, readonly) BOOL isRefreshing;
45 | @property (nonatomic, readonly) UIRefreshStatus status;
46 | @property (nonatomic, readonly) NSString *textLastRefreshTime;
47 |
48 | - (BOOL) inStatus:(UIRefreshStatus)status, ...;//多状态判断,最后一个必须是0
49 |
50 | - (void) beginRefresh;
51 | - (void) finishRefresh;
52 | - (void) finishRefreshWithSuccess:(BOOL) success;
53 | - (void) performRefreshEvent;//执行绑定事件,跳过下拉刷新,直接执行刷新代码
54 |
55 | - (void) onStartAnimationWhenRealeasing;
56 | - (void) onStartAnimationWhenRefreshing;
57 | - (void) onStatus:(UIRefreshStatus) old changed:(UIRefreshStatus) status;
58 | - (void) onScrollingWithOffset:(CGFloat) offset percent:(CGFloat) percent drag:(BOOL) isDragging;
59 | - (void) onRefreshFinished:(BOOL) success;
60 | - (CGFloat) onRefreshFinishing:(BOOL) success; //返回停留时间
61 |
62 | @end
63 |
64 | NS_ASSUME_NONNULL_END
65 |
--------------------------------------------------------------------------------
/SmartRefreshControl/Game/UIRefreshGameStartScene.m:
--------------------------------------------------------------------------------
1 | //
2 | // UIRefreshGameStartScene.m
3 | // Refresh
4 | //
5 | // Created by SCWANG on 2021/6/8.
6 | // Copyright © 2021 Teeyun. All rights reserved.
7 | //
8 |
9 | #import "UIRefreshGameStartScene.h"
10 |
11 | @interface UIRefreshGameStartScene ()
12 |
13 | @property (nonatomic, strong) SKLabelNode *nodeTitle;
14 | @property (nonatomic, strong) SKLabelNode *nodeDetail;
15 | @property (nonatomic, strong) SKShapeNode *nodeFrame;
16 |
17 | @end
18 |
19 | @implementation UIRefreshGameStartScene
20 |
21 | - (SKLabelNode *)nodeTitle {
22 | if (_nodeTitle == nil) {
23 | _nodeTitle = [SKLabelNode new];
24 | _nodeTitle.fontSize = 20;
25 | _nodeTitle.fontName = @"AvenirNext-Bold";
26 | _nodeTitle.text = @"Pull to Break Out!";
27 | [self addChild:_nodeTitle];
28 | }
29 | return _nodeTitle;
30 | }
31 |
32 | - (SKLabelNode *)nodeDetail {
33 | if (_nodeDetail == nil) {
34 | _nodeDetail = [SKLabelNode new];
35 | _nodeDetail.fontSize = 17;
36 | _nodeDetail.fontName = @"AvenirNext-Bold";
37 | _nodeDetail.text = @"Scroll to move handle";
38 | [self addChild:_nodeDetail];
39 | }
40 | return _nodeDetail;
41 | }
42 |
43 | - (SKShapeNode *)nodeFrame {
44 | if (_nodeFrame == nil) {
45 | _nodeFrame = [SKShapeNode new];
46 | _nodeFrame.lineWidth = 1;
47 | [self addChild:_nodeFrame];
48 | }
49 | return _nodeFrame;
50 | }
51 |
52 | - (void)didMoveToView:(SKView *)view {
53 | [super didMoveToView:view];
54 | [self setBackgroundColor:self.colorAccent];
55 | [self setScaleMode:SKSceneScaleModeAspectFit];
56 | [self.nodeTitle setFontColor:self.colorPrimary];
57 | [self.nodeDetail setFontColor:self.colorPrimary];
58 | [self.nodeFrame setStrokeColor:self.colorPrimary];
59 | }
60 |
61 | - (void)setSize:(CGSize)size {
62 | [super setSize:size];
63 | [self.nodeTitle setPosition:CGPointMake(size.width/2, size.height/2 + 5)];
64 | [self.nodeDetail setPosition:CGPointMake(size.width/2, size.height/2 - 15)];
65 | [self.nodeFrame setPath:CGPathCreateWithRect(CGRectMake(0, 0, size.width, size.height), nil)];
66 | }
67 |
68 | @end
69 |
--------------------------------------------------------------------------------
/SmartRefreshControl/Component/UIRefreshFooter.h:
--------------------------------------------------------------------------------
1 | //
2 | // UIRefreshFooter.h
3 | // Refresh
4 | //
5 | // Created by SCWANG on 2021/7/1.
6 | // Copyright © 2021 Teeyun. All rights reserved.
7 | //
8 |
9 | #if __has_include()
10 | #import
11 | #else
12 | #import "UIRefreshComponent.h"
13 | #endif
14 |
15 | NS_ASSUME_NONNULL_BEGIN
16 |
17 | /**
18 | * 刷新状态
19 | */
20 | typedef NS_ENUM(NSUInteger, UISmartFooterStatus) {
21 | UISmartFooterStatusIdle,
22 | UISmartFooterStatusPullToLoadMore,
23 | UISmartFooterStatusReleaseToLoadMore,
24 | UISmartFooterStatusWillLoadMore, //调用 beginLoadMore 时才会触发
25 | UISmartFooterStatusReleasing,
26 | UISmartFooterStatusLoading,
27 | UISmartFooterStatusFinish,
28 | UISmartFooterStatusNoMoreData,
29 | };
30 |
31 |
32 | @class UIRefreshFooter;
33 |
34 | typedef void(^UISmartFooterBlock)(UIRefreshFooter *footer);
35 |
36 | @interface UIRefreshFooter : UIRefreshComponent
37 |
38 | @property (nonatomic, copy) UISmartFooterBlock loadMoreBlock;
39 |
40 | @property (nonatomic, assign) BOOL isAutoLoadMore;
41 | //@property (nonatomic, assign) CGFloat triggerAutoLoadMorePercent;
42 | @property (nonatomic, readonly) BOOL isLoading;
43 | @property (nonatomic, readonly) BOOL isNoMoreData;//判断当前是否处于没有更多数据状态
44 | @property (nonatomic, readonly) UISmartFooterStatus status;
45 |
46 | - (BOOL) inStatus:(UISmartFooterStatus)status, ...;//多状态判断,最后一个必须是0
47 |
48 | //- (void) beginLoadMore;
49 | - (void) finishLoadMore;
50 | - (void) finishLoadMoreWithNoMoreData;//关闭加载更多,并标记为没有更多的数据,不再触发
51 | - (void) finishLoadMoreWithSuccess:(BOOL) success;
52 | - (void) performLoadMoreEvent;//执行绑定事件,跳过下拉刷新,直接执行刷新代码
53 |
54 | - (void) resetNoMoreData;//重置没有更多数据,可再次触发加载更多,一般用在下拉刷新之后
55 |
56 | - (void) onStartAnimationWhenLoading;
57 | - (void) onStartAnimationWhenRealeasing;
58 | - (void) onStatus:(UISmartFooterStatus) old changed:(UISmartFooterStatus) status;
59 | - (void) onScrollingWithOffset:(CGFloat) offset percent:(CGFloat) percent drag:(BOOL) isDragging;
60 | - (void) onLoadMoreFinished:(BOOL) success;
61 | - (CGFloat) onLoadMoreFinishing:(BOOL) success; //返回停留时间
62 |
63 | @end
64 |
65 | NS_ASSUME_NONNULL_END
66 |
--------------------------------------------------------------------------------
/SmartRefreshControl/Vector/ValueAnimator.h:
--------------------------------------------------------------------------------
1 | //
2 | // ValueAnimator.h
3 | // Refresh
4 | //
5 | // Created by Teeyun on 2020/9/14.
6 | // Copyright © 2020 Teeyun. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | NS_ASSUME_NONNULL_BEGIN
12 |
13 |
14 | typedef NS_ENUM(NSUInteger, AnimatorInterpolator) {
15 | AnimatorInterpolatorLinear,
16 | AnimatorInterpolatorAccelerate,
17 | AnimatorInterpolatorDecelerate,
18 | AnimatorInterpolatorAccelerateDecelerate,
19 | AnimatorInterpolatorBounce,
20 | };
21 | typedef NS_ENUM(NSUInteger, AnimatorRepeatMode) {
22 | AnimatorRepeatModeRestart,
23 | AnimatorRepeatModeReverse,
24 | };
25 |
26 | @class ValueAnimator;
27 | @protocol ValueAnimatorDelegate
28 |
29 | @optional
30 | - (void) onAnimationStart:(ValueAnimator*) animator;
31 | - (void) onAnimationRepeat:(ValueAnimator*) animator;
32 | - (void) onAnimationEnd:(ValueAnimator*) animator;
33 |
34 | @end
35 |
36 | typedef CGFloat(^AnimatorInterpolatorBlock)(CGFloat);
37 |
38 | @interface ValueAnimator : NSObject
39 |
40 | + (ValueAnimator*)newWithFrom:(CGFloat) from to:(CGFloat) to;
41 | + (ValueAnimator*)newWithTarget:(id) target selector:(SEL) action;
42 |
43 | - (void)setTarget:(id)target selector:(SEL)action;
44 | - (void)setFrom:(CGFloat) from to:(CGFloat) to; //设置开始结束
45 | - (void)setFromToPoints:(NSArray*) points; //设置多断点
46 |
47 | @property (nonatomic, assign) CGFloat fromValue;
48 | @property (nonatomic, assign) CGFloat toValue;
49 | @property (nonatomic, assign) CGFloat speed;
50 | @property (nonatomic, assign) CGFloat repeatCount; //INFINITY 表示无限
51 | @property (nonatomic, assign) CFTimeInterval beginTime;
52 | @property (nonatomic, assign) CFTimeInterval duration; //动画持续时间(秒)
53 | @property (nonatomic, assign) AnimatorRepeatMode repeatMode;
54 | @property (nonatomic, assign) AnimatorInterpolator interpolator;
55 |
56 | @property (nonatomic, weak) id delegate;
57 | @property (nonatomic, copy) AnimatorInterpolatorBlock interpolatorBlock;
58 |
59 | @property (nonatomic, readonly) BOOL isRunning;
60 | @property (nonatomic, readonly) CGFloat value;
61 | @property (nonatomic, readonly) CGFloat percent;
62 |
63 | - (void) start;
64 | - (void) stop;
65 |
66 | @end
67 |
68 | NS_ASSUME_NONNULL_END
69 |
--------------------------------------------------------------------------------
/SmartRefreshDemo/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 | LSRequiresIPhoneOS
22 |
23 | UIApplicationSceneManifest
24 |
25 | UIApplicationSupportsMultipleScenes
26 |
27 | UISceneConfigurations
28 |
29 | UIWindowSceneSessionRoleApplication
30 |
31 |
32 | UISceneConfigurationName
33 | Default Configuration
34 | UISceneDelegateClassName
35 | $(PRODUCT_MODULE_NAME).SceneDelegate
36 | UISceneStoryboardFile
37 | Main
38 |
39 |
40 |
41 |
42 | UIApplicationSupportsIndirectInputEvents
43 |
44 | UILaunchStoryboardName
45 | LaunchScreen
46 | UIMainStoryboardFile
47 | Main
48 | UIRequiredDeviceCapabilities
49 |
50 | armv7
51 |
52 | UISupportedInterfaceOrientations
53 |
54 | UIInterfaceOrientationPortrait
55 | UIInterfaceOrientationLandscapeLeft
56 | UIInterfaceOrientationLandscapeRight
57 |
58 | UISupportedInterfaceOrientations~ipad
59 |
60 | UIInterfaceOrientationPortrait
61 | UIInterfaceOrientationPortraitUpsideDown
62 | UIInterfaceOrientationLandscapeLeft
63 | UIInterfaceOrientationLandscapeRight
64 |
65 |
66 |
67 |
--------------------------------------------------------------------------------
/SmartRefreshControl/Game/UIRefreshGamePlayingScene.m:
--------------------------------------------------------------------------------
1 | //
2 | // UIRefreshGamePlayingScene.m
3 | // Refresh
4 | //
5 | // Created by SCWANG on 2021/6/8.
6 | // Copyright © 2021 Teeyun. All rights reserved.
7 | //
8 |
9 | #import "UIRefreshGamePlayingScene.h"
10 |
11 | @interface UIRefreshGamePlayingScene ()
12 |
13 | @property (nonatomic, strong) SKLabelNode *nodeTitle;
14 |
15 | @end
16 |
17 | @implementation UIRefreshGamePlayingScene
18 |
19 | - (void)setTitle:(NSString *)title {
20 | self.nodeTitle.text = title;
21 | }
22 |
23 | - (SKLabelNode *)nodeTitle {
24 | if (_nodeTitle == nil) {
25 | _nodeTitle = [SKLabelNode new];
26 | _nodeTitle.fontSize = 20;
27 | _nodeTitle.fontName = @"AvenirNext-Bold";
28 | _nodeTitle.text = self.title;
29 | [self addChild:_nodeTitle];
30 | }
31 | return _nodeTitle;
32 | }
33 |
34 | - (void)didMoveToView:(SKView *)view {
35 | [super didMoveToView:view];
36 | // [self setBackgroundColor:self.colorPrimary];
37 | [self setScaleMode:SKSceneScaleModeAspectFit];
38 | [self.nodeTitle setFontColor:self.colorAccent];
39 | }
40 |
41 |
42 | - (void)setColorPrimary:(UIColor *)colorPrimary {
43 | _colorPrimary = colorPrimary;
44 | [self setBackgroundColor:colorPrimary];
45 | }
46 |
47 | - (void)setColorAccent:(UIColor *)colorAccent {
48 | _colorAccent = colorAccent;
49 | [self.nodeTitle setFontColor:colorAccent];
50 | }
51 |
52 | //- (void)setSize:(CGSize)size {
53 | // [super setSize:size];
54 | // [self.nodeTitle setPosition:CGPointMake(size.width/2, size.height/2)];
55 | //}
56 |
57 | - (void)didChangeSize:(CGSize)oldSize {
58 | CGSize size = self.size;
59 | [super didChangeSize:oldSize];
60 | [self.nodeTitle setPosition:CGPointMake(size.width/2, size.height/2)];
61 | }
62 |
63 | - (void)reset {
64 | [self setTitle:@"None"];
65 | }
66 |
67 | - (void)start {
68 | [self setTitle:@"Loading..."];
69 | }
70 |
71 | - (void) gameWin {
72 | [self setTitle:@"Game Win"];
73 | }
74 |
75 | - (void)gameOver {
76 | [self setTitle:@"Game Over"];
77 | }
78 |
79 | - (void)moveHandle:(CGFloat)percent {
80 |
81 | }
82 |
83 | - (void)finishWithSuccess:(BOOL)success {
84 | if (success) {
85 | [self setTitle:@"Loading Finished"];
86 | } else {
87 | [self setTitle:@"Loading Failed!"];
88 | }
89 | }
90 |
91 | @end
92 |
--------------------------------------------------------------------------------
/SmartRefreshDemo/SceneDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SceneDelegate.swift
3 | // SmartRefreshDemo
4 | //
5 | // Created by SCWANG on 2021/11/4.
6 | //
7 |
8 | import UIKit
9 |
10 | class SceneDelegate: UIResponder, UIWindowSceneDelegate {
11 |
12 | var window: UIWindow?
13 |
14 |
15 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
16 | // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
17 | // If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
18 | // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).
19 | guard let _ = (scene as? UIWindowScene) else { return }
20 | }
21 |
22 | func sceneDidDisconnect(_ scene: UIScene) {
23 | // Called as the scene is being released by the system.
24 | // This occurs shortly after the scene enters the background, or when its session is discarded.
25 | // Release any resources associated with this scene that can be re-created the next time the scene connects.
26 | // The scene may re-connect later, as its session was not necessarily discarded (see `application:didDiscardSceneSessions` instead).
27 | }
28 |
29 | func sceneDidBecomeActive(_ scene: UIScene) {
30 | // Called when the scene has moved from an inactive state to an active state.
31 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive.
32 | }
33 |
34 | func sceneWillResignActive(_ scene: UIScene) {
35 | // Called when the scene will move from an active state to an inactive state.
36 | // This may occur due to temporary interruptions (ex. an incoming phone call).
37 | }
38 |
39 | func sceneWillEnterForeground(_ scene: UIScene) {
40 | // Called as the scene transitions from the background to the foreground.
41 | // Use this method to undo the changes made on entering the background.
42 | }
43 |
44 | func sceneDidEnterBackground(_ scene: UIScene) {
45 | // Called as the scene transitions from the foreground to the background.
46 | // Use this method to save data, release shared resources, and store enough scene-specific state information
47 | // to restore the scene back to its current state.
48 | }
49 |
50 |
51 | }
52 |
53 |
--------------------------------------------------------------------------------
/SmartRefreshControl/SmartRefreshControl.h:
--------------------------------------------------------------------------------
1 | //
2 | // SmartRefreshControl.h
3 | // SmartRefreshControl
4 | //
5 | // Created by SCWANG on 2021/11/4.
6 | //
7 |
8 | #import
9 |
10 |
11 | #if __has_include()
12 |
13 | //! Project version number for SmartRefreshControl.
14 | FOUNDATION_EXPORT double SmartRefreshControlVersionNumber;
15 |
16 | //! Project version string for SmartRefreshControl.
17 | FOUNDATION_EXPORT const unsigned char SmartRefreshControlVersionString[];
18 |
19 | // In this header, you should import all the public headers of your framework using statements like #import
20 |
21 | #import
22 | #import
23 | #import
24 |
25 | #import
26 | #import
27 | #import
28 | #import
29 | #import
30 | #import
31 | #import
32 | #import
33 | #import
34 | #import
35 | #import
36 | #import
37 | #import
38 | #import
39 |
40 | #import
41 |
42 | #else
43 |
44 | #import "UIRefreshFooter.h"
45 | #import "UIRefreshHeader.h"
46 | #import "UIRefreshComponent.h"
47 |
48 | #import "UIRefreshClassicsHeader.h"
49 | #import "UIRefreshTaurusHeader.h"
50 | #import "UIRefreshPhoenixHeader.h"
51 | #import "UIRefreshDropBoxHeader.h"
52 | #import "UIRefreshDeliveryHeader.h"
53 | #import "UIRefreshBezierRadarHeader.h"
54 | #import "UIRefreshBezierCircleHeader.h"
55 | #import "UIRefreshOriginalHeader.h"
56 | #import "UIRefreshMaterialHeader.h"
57 | #import "UIRefreshWaveSwipeHeader.h"
58 | #import "UIRefreshStoreHouseHeader.h"
59 | #import "UIRefreshGameHitBlockHeader.h"
60 | #import "UIRefreshGameBattleCityHeader.h"
61 | #import "UIRefreshFlyHeader.h"
62 |
63 | #import "UIRefreshClassicsFooter.h"
64 |
65 | #endif
66 |
67 |
--------------------------------------------------------------------------------
/SmartRefreshControl/Vector/Utilities.m:
--------------------------------------------------------------------------------
1 | //
2 | // Utilities.m
3 | // Refresh
4 | //
5 | // Created by Teeyun on 2020/9/8.
6 | // Copyright © 2020 Teeyun. All rights reserved.
7 | //
8 | #import
9 | #import "Utilities.h"
10 | #import "Element.h"
11 |
12 | #define kElementIdentifier @"SVGElementIdentifier"
13 |
14 | @implementation Utilities
15 |
16 |
17 | +(CALayer *) newLayerForElement:(Element*) element withPath:(CGPathRef) path
18 | {
19 | CAShapeLayer* _shapeLayer = [CAShapeLayer layer];
20 |
21 | _shapeLayer.name = element.identifier;
22 | [_shapeLayer setValue:element.identifier forKey:kElementIdentifier];
23 |
24 | CGFloat strokeWidth = element.strokeWidth;
25 |
26 | CGRect pathRect = CGRectInset(CGPathGetBoundingBox(path), -strokeWidth/2., -strokeWidth/2.);
27 |
28 | _shapeLayer.path = path; //CGPathRelease(finalPath);
29 | _shapeLayer.frame = pathRect;
30 |
31 | CGRect localRect = CGRectMake(0, 0, CGRectGetWidth(pathRect), CGRectGetHeight(pathRect));
32 |
33 | //DEBUG ONLY: CGRect shapeLayerFrame = _shapeLayer.frame;
34 | CAShapeLayer* strokeLayer = _shapeLayer;
35 | CAShapeLayer* fillLayer = _shapeLayer;
36 |
37 | if( strokeWidth > 0 && element.stroke != nil )
38 | {
39 | CGSize fakeSize = CGSizeMake( strokeWidth, strokeWidth );
40 | strokeLayer.lineWidth = hypot(fakeSize.width, fakeSize.height)/M_SQRT2;
41 | strokeLayer.strokeColor = element.stroke.CGColor;
42 | }
43 | else
44 | {
45 | if( element.stroke != nil )
46 | {
47 | strokeLayer.strokeColor = nil; // This is how you tell Apple that the stroke is disabled; a strokewidth of 0 will NOT achieve this
48 | strokeLayer.lineWidth = 0.0f; // MUST set this explicitly, or Apple assumes 1.0
49 | }
50 | else
51 | {
52 | strokeLayer.lineWidth = 1.0f; // default value from SVG spec
53 | }
54 | }
55 |
56 | //CGFloat alpha = 1;
57 | //[element.fill getRed:nil green:nil blue:nil alpha:&alpha];
58 |
59 | fillLayer.fillColor = element.fill.CGColor;
60 | fillLayer.opacity = element.opacity;//*alpha;
61 |
62 | if (strokeLayer == fillLayer)
63 | {
64 | return strokeLayer;
65 | }
66 |
67 | CALayer* combined = [CALayer layer];
68 | combined.frame = strokeLayer.frame;
69 | strokeLayer.frame = localRect;
70 | if ([strokeLayer isKindOfClass:[CAShapeLayer class]])
71 | strokeLayer.fillColor = nil;
72 | fillLayer.frame = localRect;
73 | [combined addSublayer:fillLayer];
74 | [combined addSublayer:strokeLayer];
75 | return combined;
76 | }
77 |
78 | @end
79 |
--------------------------------------------------------------------------------
/SmartRefresh.xcodeproj/xcshareddata/xcschemes/SmartRefreshDemo.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
32 |
33 |
43 |
45 |
51 |
52 |
53 |
54 |
60 |
62 |
68 |
69 |
70 |
71 |
73 |
74 |
77 |
78 |
79 |
--------------------------------------------------------------------------------
/SmartRefreshControl/Component/UIVectorView.m:
--------------------------------------------------------------------------------
1 | //
2 | // UIVectorView.m
3 | // Refresh
4 | //
5 | // Created by Teeyun on 2020/9/10.
6 | // Copyright © 2020 Teeyun. All rights reserved.
7 | //
8 |
9 | #import "UIVectorView.h"
10 |
11 | @implementation UIVectorView
12 |
13 | - (instancetype)initWithFrame:(CGRect)frame
14 | {
15 | self = [super initWithFrame:frame];
16 | if (self) {
17 | self.backgroundColor = [UIColor clearColor];
18 | }
19 | return self;
20 | }
21 |
22 | - (instancetype)initWithCoder:(NSCoder *)coder {
23 | self = [super initWithCoder:coder];
24 | if (self) {
25 | self.backgroundColor = [UIColor clearColor];
26 | }
27 | return self;
28 | }
29 |
30 | - (void)parserPaths:(NSArray *)paths {
31 | if (self.image == nil) {
32 | self.image = [VectorImage new];
33 | }
34 | [self.image parserPaths:paths];
35 | }
36 |
37 | - (void)parserColors:(NSArray *)colors {
38 | if (self.image != nil) {
39 | [self.image parserColors:colors];
40 | }
41 | }
42 |
43 | - (void)setFrame:(CGRect)frame {
44 | [super setFrame:frame];
45 | if (self.image != NULL) {
46 | [self.image scaleToFitInside:frame.size];
47 | }
48 | }
49 |
50 | - (void)setTintColor:(UIColor *)tintColor {
51 | [super setTintColor:tintColor];
52 |
53 | VectorImage *image = self.image;
54 | if (image) {
55 | NSUInteger count = image.elements.count;
56 | NSMutableArray *colors = [NSMutableArray arrayWithCapacity:count];
57 | for (int i = 0; i < count; i++) {
58 | [colors addObject:tintColor];
59 | }
60 | [image parserColors:colors];
61 | }
62 | }
63 |
64 | - (void)setVectorImage:(VectorImage *)image {
65 | _image = image;
66 |
67 | CGSize size = self.frame.size;
68 | if (!CGSizeEqualToSize(CGSizeZero, size)) {
69 | [image scaleToFitInside:size];
70 | }
71 | }
72 |
73 | - (void)sizeToFit {
74 | VectorImage *image = self.image;
75 | if (image) {
76 | CGRect frame = self.frame;
77 | frame.size = image.viewport.size;
78 | self.frame = frame;
79 | } else {
80 | [super sizeToFit];
81 | }
82 | }
83 |
84 | - (CGSize)sizeThatFits:(CGSize)size {
85 | VectorImage *image = self.image;
86 | if (image) {
87 | CGSize sizeImage = image.viewport.size;
88 | if (sizeImage.width > size.width || sizeImage.height > size.height) {
89 | CGSize old = image.size;
90 | [image scaleToFitInside:size];
91 | size = image.size;
92 | image.size = old;
93 | return size;
94 | }
95 | return image.viewport.size;
96 | } else {
97 | return [super sizeThatFits:size];
98 | }
99 | }
100 |
101 | // Only override drawRect: if you perform custom drawing.
102 | // An empty implementation adversely affects performance during animation.
103 | - (void)drawRect:(CGRect)rect {
104 | // Drawing code
105 | VectorImage *image = self.image;
106 | if (image) {
107 | CGContextRef context = UIGraphicsGetCurrentContext();
108 | CGContextSaveGState(context);
109 |
110 | CGSize sizeImage = image.size;
111 | CGSize sizeCanvas = self.bounds.size;
112 |
113 | CGContextTranslateCTM(context, sizeCanvas.width/2-sizeImage.width/2, sizeCanvas.height/2-sizeImage.height/2 );
114 |
115 | [self.image renderInContext:context];
116 |
117 | CGContextRestoreGState(context);
118 | } else {
119 | [super drawRect:rect];
120 | }
121 | }
122 |
123 |
124 | @end
125 |
--------------------------------------------------------------------------------
/SmartRefreshControl/Component/UIRefreshComponent.h:
--------------------------------------------------------------------------------
1 | //
2 | // UIRefreshComponent.h
3 | // Teecloud
4 | //
5 | // Created by Teeyun on 2020/8/17.
6 | // Copyright © 2020 SCWANG. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | NS_ASSUME_NONNULL_BEGIN
12 |
13 | /**
14 | * 滚动模式
15 | */
16 | typedef NS_ENUM(NSInteger, UISmartScrollMode) {
17 | UISmartScrollModeMove, //移动
18 | UISmartScrollModeStretch, //拉伸
19 | UISmartScrollModeFront, //前置
20 | };
21 |
22 | ///**
23 | // * 刷新状态
24 | // */
25 | //typedef NS_ENUM(NSUInteger, UIRefreshStatus) {
26 | // UIRefreshStatusIdle,
27 | // UIRefreshStatusPullToRefresh,
28 | // UIRefreshStatusReleaseToRefresh,
29 | // UIRefreshStatusWillRefresh, //调用 beginRefresh 时才会触发
30 | // UIRefreshStatusReleasing,
31 | // UIRefreshStatusRefreshing,
32 | // UIRefreshStatusFinish,
33 | //};
34 |
35 | @interface UIRefreshComponent : UIControl
36 |
37 | @property (nonatomic, assign) CGFloat width;
38 | @property (nonatomic, assign) CGFloat height; //高度,拉伸类型时刻变化,请使用 expandHeight
39 | @property (nonatomic, assign) CGFloat triggerRate; //触发刷新距离 与 展开具体 的比率(默认1)
40 | @property (nonatomic, assign) CGFloat finishDuration; //刷新完成时,停留时间(秒)
41 |
42 | @property (nonatomic, assign) NSTimeInterval durationFast;
43 | @property (nonatomic, assign) NSTimeInterval durationNormal;
44 | @property (nonatomic, assign) UISmartScrollMode scrollMode;
45 |
46 | @property (nonatomic, strong) UIColor *colorAccent;
47 | @property (nonatomic, strong) UIColor *colorPrimary;
48 |
49 | @property (nonatomic, readonly) BOOL isAttached;
50 | @property (nonatomic, readonly) BOOL isExpanded;
51 | //@property (nonatomic, readonly) BOOL isRefreshing;
52 | @property (nonatomic, readonly) CGFloat dragOffset;
53 | @property (nonatomic, readonly) CGFloat dragPercent;
54 | @property (nonatomic, readonly) CGFloat expandHeight; //展开的高度,是一个设置的固定值
55 | //@property (nonatomic, readonly) BOOL isIgnoreObserve;
56 | //@property (nonatomic, readonly) UIRefreshStatus status;
57 | //@property (nonatomic, readonly) UIEdgeInsets originalInset;
58 |
59 | @property (nonatomic, readonly, weak) id target;
60 | @property (nonatomic, readonly, weak) UIScrollView *scrollView;
61 |
62 | - (void) setUpComponent NS_REQUIRES_SUPER;
63 | - (void) addObservers:(UIScrollView*) scrollView NS_REQUIRES_SUPER;
64 | - (void) removeObservers:(UIScrollView*) scrollView NS_REQUIRES_SUPER;
65 | //- (void) adjustInset:(UIScrollView*) scrollView expand:(BOOL) expand;
66 |
67 | - (void) scrollView:(UIScrollView*) scrollView attached:(BOOL) attach NS_REQUIRES_SUPER;
68 | - (void) scrollView:(UIScrollView*) scrollView detached:(BOOL) detach NS_REQUIRES_SUPER;
69 | - (void) scrollView:(UIScrollView*) scrollView didChange:(CGPoint) old contentOffset:(CGPoint) value NS_REQUIRES_SUPER;
70 | - (void) scrollView:(UIScrollView *)scrollView didChange:(UIEdgeInsets) old contentInset:(UIEdgeInsets)value NS_REQUIRES_SUPER;
71 | //- (void) scrollView:(UIScrollView*) scrollView didChange:(UIRefreshStatus) old status:(UIRefreshStatus) status;
72 | - (void) scrollView:(UIScrollView*) scrollView didScroll:(CGFloat) offset percent:(CGFloat) percent drag:(BOOL) isDragging;
73 |
74 | - (void) adjustFrameWithHeight:(CGFloat)expandHeight inset:(CGFloat)insetTop expand:(BOOL)isExpanded offset:(CGFloat)offset;
75 |
76 | - (CGFloat) finalyContentInsetsFrom:(UIScrollView*) scrollView;
77 |
78 | //API-method
79 | - (void) attach:(UIScrollView*) scrollView;
80 | //- (void) performRefreshEvent;//执行绑定事件,跳过下拉刷新,直接执行刷新代码
81 | - (void) addTarget:(id) target action:(SEL) action;//添加刷新事件监听
82 | //- (BOOL) inStatus:(UIRefreshStatus)status, ...;//多状态判断,最后一个必须是0
83 |
84 | //API-static
85 | + (instancetype) attach:(UIScrollView*) scrollView;
86 | + (instancetype) attach:(UIScrollView*) scrollView target:(id) target action:(SEL) action;
87 |
88 | @end
89 |
90 | NS_ASSUME_NONNULL_END
91 |
--------------------------------------------------------------------------------
/SmartRefreshControl/Vector/PathsParser.h:
--------------------------------------------------------------------------------
1 | //
2 | // PointsAndPathsParser.h
3 | // Refresh
4 | //
5 | // Created by Teeyun on 2020/9/8.
6 | // Copyright © 2020 Teeyun. All rights reserved.
7 | //
8 |
9 | #import
10 | #import
11 |
12 | NS_ASSUME_NONNULL_BEGIN
13 |
14 | /**
15 | * Partially spammy; not as spammy as DEBUG_PATH_CREATION
16 | */
17 | #define VERBOSE_PARSE_SVG_COMMAND_STRINGS 0
18 |
19 | /*! Very useful for debugging the parser - this will output one line of logging
20 | * for every CGPath command that's actually done; you can then compare these lines
21 | * to the input source file, and manually check what's being sent to the renderer
22 | * versus what was expected
23 | *
24 | * this is MORE SPAMMY than VERBOSE_PARSE_SVG_COMMAND_STRINGS
25 | */
26 | #define DEBUG_PATH_CREATION 0
27 |
28 | typedef NS_ENUM(NSUInteger, CurveType) {
29 | CurveTypePoint,
30 | CurveTypeCubic,
31 | CurveTypeQuadratic,
32 | };
33 |
34 | typedef struct Curve
35 | {
36 | CurveType type;
37 | CGPoint c1;
38 | CGPoint c2;
39 | CGPoint p;
40 | } Curve;
41 |
42 | @interface PathsParser : NSObject
43 |
44 | + (Curve) startingCurve;
45 |
46 | + (Curve) readMovetoDrawToCmdGroups:(NSScanner*)scanner path:(CGMutablePathRef)path relativeTo:(CGPoint)origin isRelative:(BOOL) isRelative;
47 | + (Curve) readMovetoDrawto:(NSScanner*)scanner path:(CGMutablePathRef)path relativeTo:(CGPoint)origin isRelative:(BOOL) isRelative;
48 | + (Curve) readMoveto:(NSScanner*)scanner path:(CGMutablePathRef)path relativeTo:(CGPoint)origin isRelative:(BOOL) isRelative;
49 | + (Curve) readMovetoArgumentSequence:(NSScanner*)scanner path:(CGMutablePathRef)path relativeTo:(CGPoint)origin isRelative:(BOOL) isRelative;
50 |
51 | + (Curve) readLineToCmd:(NSScanner*)scanner path:(CGMutablePathRef)path relativeTo:(CGPoint)origin isRelative:(BOOL) isRelative;
52 | + (Curve) readLinetoArgumentSequence:(NSScanner*)scanner path:(CGMutablePathRef)path relativeTo:(CGPoint)origin isRelative:(BOOL) isRelative;
53 | + (Curve) readVerticalLineToCmd:(NSScanner*)scanner path:(CGMutablePathRef)path relativeTo:(CGPoint)origin;
54 | + (Curve) readVerticalLinetoArgumentSequence:(NSScanner*)scanner path:(CGMutablePathRef)path relativeTo:(CGPoint)origin;
55 | + (Curve) readHorizontalLinetoArgumentSequence:(NSScanner*)scanner path:(CGMutablePathRef)path relativeTo:(CGPoint)origin;
56 | + (Curve) readHorizontalLineToCmd:(NSScanner*)scanner path:(CGMutablePathRef)path relativeTo:(CGPoint)origin;
57 |
58 | + (Curve) readQuadraticCurveToCmd:(NSScanner*)scanner path:(CGMutablePathRef)path relativeTo:(CGPoint)origin isRelative:(BOOL) isRelative;
59 | + (Curve) readQuadraticCurvetoArgumentSequence:(NSScanner*)scanner path:(CGMutablePathRef)path relativeTo:(CGPoint)origin isRelative:(BOOL) isRelative;
60 | + (Curve) readQuadraticCurvetoArgument:(NSScanner*)scanner path:(CGMutablePathRef)path relativeTo:(CGPoint)origin;
61 | + (Curve) readSmoothQuadraticCurveToCmd:(NSScanner*)scanner path:(CGMutablePathRef)path relativeTo:(CGPoint)origin withPrevCurve:(Curve)prevCurve;
62 | + (Curve) readSmoothQuadraticCurvetoArgumentSequence:(NSScanner*)scanner path:(CGMutablePathRef)path relativeTo:(CGPoint)origin withPrevCurve:(Curve)prevCurve;
63 | + (Curve) readSmoothQuadraticCurvetoArgument:(NSScanner*)scanner path:(CGMutablePathRef)path relativeTo:(CGPoint)origin withPrevCurve:(Curve)prevCurve;
64 |
65 | + (Curve) readCurveToCmd:(NSScanner*)scanner path:(CGMutablePathRef)path relativeTo:(CGPoint)origin isRelative:(BOOL) isRelative;
66 | + (Curve) readCurvetoArgumentSequence:(NSScanner*)scanner path:(CGMutablePathRef)path relativeTo:(CGPoint)origin isRelative:(BOOL) isRelative;
67 | + (Curve) readCurvetoArgument:(NSScanner*)scanner path:(CGMutablePathRef)path relativeTo:(CGPoint)origin;
68 | + (Curve) readSmoothCurveToCmd:(NSScanner*)scanner path:(CGMutablePathRef)path relativeTo:(CGPoint)origin withPrevCurve:(Curve)prevCurve isRelative:(BOOL) isRelative;
69 | + (Curve) readSmoothCurvetoArgumentSequence:(NSScanner*)scanner path:(CGMutablePathRef)path relativeTo:(CGPoint)origin withPrevCurve:(Curve)prevCurve isRelative:(BOOL) isRelative;
70 | + (Curve) readSmoothCurvetoArgument:(NSScanner*)scanner path:(CGMutablePathRef)path relativeTo:(CGPoint)origin withPrevCurve:(Curve)prevCurve;
71 |
72 | + (Curve) readEllipticalArcArguments:(NSScanner*)scanner path:(CGMutablePathRef)path relativeTo:(CGPoint)origin isRelative:(BOOL) isRelative;
73 |
74 | + (Curve) readCloseCommand:(NSScanner*)scanner path:(CGMutablePathRef)path relativeTo:(CGPoint)origin;
75 |
76 | @end
77 |
78 | NS_ASSUME_NONNULL_END
79 |
--------------------------------------------------------------------------------
/SmartRefreshControl/Vector/VectorImage.m:
--------------------------------------------------------------------------------
1 | //
2 | // VectorImage.m
3 | // Refresh
4 | //
5 | // Created by Teeyun on 2020/9/10.
6 | // Copyright © 2020 Teeyun. All rights reserved.
7 | //
8 |
9 | #import "VectorImage.h"
10 | #import "PathElement.h"
11 |
12 | @interface VectorImage ()
13 |
14 | @property (nonatomic, strong) NSMutableArray *elements;
15 |
16 | @end
17 |
18 | @implementation VectorImage
19 |
20 | - (instancetype)init
21 | {
22 | self = [super init];
23 | if (self) {
24 | self.size = CGSizeZero;
25 | self.frame = CGRectZero;
26 | self.viewport = CGRectZero;
27 | self.elements = [NSMutableArray array];
28 | }
29 | return self;
30 | }
31 |
32 | - (CGSize)size {
33 | if (self.elements.count > 0 && CGRectEqualToRect(_frame, CGRectZero)) {
34 | return self.viewport.size;
35 | }
36 | return self.frame.size;
37 | }
38 |
39 | - (void)setSize:(CGSize)size {
40 | CGRect frame = self.frame;
41 | frame.size = size;
42 | self.frame = frame;
43 | }
44 |
45 | - (void)setOpacity:(CGFloat)opacity {
46 | _opacity = opacity;
47 |
48 | if (_elements) {
49 | for (Element* element in _elements) {
50 | element.opacity = opacity;
51 | }
52 | }
53 | }
54 |
55 | - (void)addElement:(Element *)element {
56 | [_elements addObject:element];
57 | self.viewport = CGRectUnion(self.viewport, element.viewport);
58 | }
59 |
60 | - (void)parserPaths:(NSArray *)paths {
61 | _viewport = CGRectZero;
62 | [_elements removeAllObjects];
63 | for (NSString* path in paths) {
64 | [self addElement:[PathElement newWith:path]];
65 | }
66 | }
67 |
68 | - (void)parserColors:(NSArray *)colors {
69 | if (_elements.count == colors.count) {
70 | for (int i = 0; i < colors.count; i++) {
71 | _elements[i].fill = colors[i];
72 | }
73 | } else if (_elements.count != colors.count && colors.count > 0) {
74 | UIColor* color = colors[0];
75 | for (int i = 0; i < _elements.count; i++) {
76 | if (i < colors.count) {
77 | color = colors[i];
78 | }
79 | _elements[i].fill = color;
80 | }
81 | }
82 | }
83 |
84 | - (void)renderInContext:(CGContextRef)context {
85 | if (!CGRectEqualToRect(self.viewport, CGRectZero)) {
86 |
87 | CGPoint point = self.frame.origin;
88 | CGPoint start = self.viewport.origin;
89 | CGSize sizeCanvas = self.size;
90 | CGSize sizeImage = self.viewport.size;
91 | CGFloat scaleX = sizeCanvas.width / sizeImage.width;
92 | CGFloat scaleY = sizeCanvas.height / sizeImage.height;
93 |
94 | CGContextSaveGState(context);
95 |
96 | CGContextTranslateCTM(context, point.x , point.y);
97 | CGContextTranslateCTM(context, -scaleX*start.x , -scaleY*start.y);
98 | CGContextScaleCTM(context, scaleX, scaleY);
99 |
100 | for (Element* element in self.elements) {
101 | [element.layer renderInContext:context];
102 | }
103 |
104 | CGContextRestoreGState(context);
105 | }
106 | }
107 |
108 | -(void) scaleToFitInside:(CGSize) maxSize
109 | {
110 | if (CGSizeEqualToSize(CGSizeZero, self.size) || CGSizeEqualToSize(CGSizeZero, maxSize)) {
111 | self.size = maxSize;
112 | return;
113 | }
114 |
115 | float wScale = maxSize.width / self.size.width;
116 | float hScale = maxSize.height / self.size.height;
117 |
118 | float smallestScaleUp = MIN( wScale, hScale );
119 |
120 | // if( smallestScaleUp < 1.0f )
121 | // smallestScaleUp = MAX( wScale, hScale ); // instead of scaling-up the smallest, scale-down the largest
122 |
123 | self.size = CGSizeApplyAffineTransform( self.size, CGAffineTransformMakeScale( smallestScaleUp, smallestScaleUp));
124 | }
125 |
126 | - (void)setGeometricWidth:(CGFloat) width {
127 | CGSize size = self.size;
128 | if (!CGSizeEqualToSize(size, CGSizeZero)) {
129 | [self setSize:CGSizeMake(width, size.height * width / size.width)];
130 | }
131 | }
132 |
133 | - (void)setGeometricHeight:(CGFloat) height {
134 | CGSize size = self.size;
135 | if (!CGSizeEqualToSize(size, CGSizeZero)) {
136 | [self setSize:CGSizeMake(size.width * height / size.height, height)];
137 | }
138 | }
139 |
140 | - (void)moveTo:(CGPoint)pt {
141 | CGSize size = self.size;
142 | self.frame = CGRectMake(pt.x, pt.y, size.width, size.height);
143 | }
144 |
145 | @end
146 |
--------------------------------------------------------------------------------
/SmartRefreshControl/Game/UIRefreshGameHeader.m:
--------------------------------------------------------------------------------
1 | //
2 | // UIRefreshGameHeader.m
3 | // Refresh
4 | //
5 | // Created by SCWANG on 2021/6/6.
6 | // Copyright © 2021 Teeyun. All rights reserved.
7 | //
8 | #import
9 |
10 | #import "UIRefreshGameHeader.h"
11 | #import "UIRefreshGameStartScene.h"
12 | #import "UIRefreshGamePlayingScene.h"
13 |
14 | @interface UIRefreshHeader (UIRefreshGameHeader)
15 |
16 | @property (nonatomic, assign) BOOL isSuccess;
17 |
18 | @end
19 |
20 | @interface UIRefreshGameHeader ()
21 |
22 | @property (nonatomic, assign) BOOL requestFinish;
23 | @property (nonatomic, strong) SKView *gameView;
24 | @property (nonatomic, strong) UIRefreshGameStartScene *sceneStart;
25 | @property (nonatomic, strong) UIRefreshGamePlayingScene *scenePlaying;
26 |
27 | - (UIRefreshGamePlayingScene*) newPlayingScene;
28 |
29 | @end
30 |
31 | @implementation UIRefreshGameHeader
32 |
33 | - (void)setUpComponent {
34 | [super setUpComponent];
35 |
36 | [self setHeight:100];
37 | [self setColorAccent:UIColor.whiteColor];
38 | [self setColorPrimary:UIColor.darkGrayColor];
39 | [self setScrollMode:UISmartScrollModeMove];
40 | }
41 |
42 | - (SKView *)gameView {
43 | if (_gameView == nil) {
44 | _gameView = [SKView new];
45 | }
46 | return _gameView;
47 | }
48 |
49 | - (UIRefreshGameStartScene *)sceneStart {
50 | if (_sceneStart == nil) {
51 | _sceneStart = [UIRefreshGameStartScene new];
52 | _sceneStart.colorAccent = self.colorAccent;
53 | _sceneStart.colorPrimary = self.colorPrimary;
54 | }
55 | return _sceneStart;
56 | }
57 |
58 | - (UIRefreshGamePlayingScene *)scenePlaying {
59 | if (_scenePlaying == nil) {
60 | _scenePlaying = [self newPlayingScene];
61 | _scenePlaying.colorAccent = self.colorAccent;
62 | _scenePlaying.colorPrimary = self.colorPrimary;
63 | }
64 | return _scenePlaying;
65 | }
66 |
67 | - (void)setFrame:(CGRect)frame {
68 | if (self.isAttached && self.frame.size.width != frame.size.width) {
69 | [super setFrame:frame];
70 | [self.sceneStart setSize:self.bounds.size];
71 | [self.scenePlaying setSize:self.bounds.size];
72 | [self.gameView setFrame:self.bounds];
73 | } else {
74 | [super setFrame:frame];
75 | }
76 | }
77 |
78 | - (void)setColorPrimary:(UIColor *)colorPrimary {
79 | [super setColorPrimary:colorPrimary];
80 | [self.scenePlaying setColorPrimary:colorPrimary];
81 | [self.sceneStart setColorPrimary:colorPrimary];
82 | }
83 |
84 | - (void)setColorAccent:(UIColor *)colorAccent {
85 | [super setColorAccent:colorAccent];
86 | [self.scenePlaying setColorAccent:colorAccent];
87 | [self.sceneStart setColorAccent:colorAccent];
88 | }
89 |
90 |
91 | - (UIRefreshGamePlayingScene *)newPlayingScene {
92 | return [UIRefreshGamePlayingScene new];
93 | }
94 |
95 | - (void)scrollView:(UIScrollView *)scrollView attached:(BOOL)attach {
96 | [super scrollView:scrollView attached:attach];
97 | [self.sceneStart setSize:self.bounds.size];
98 | [self.scenePlaying setSize:self.bounds.size];
99 | [self.gameView setFrame:self.bounds];
100 | [self.gameView presentScene:self.sceneStart];
101 | [self addSubview:self.gameView];
102 | }
103 |
104 | - (void)scrollView:(UIScrollView *)scrollView didScroll:(CGFloat)offset percent:(CGFloat)percent drag:(BOOL)isDragging {
105 | [super scrollView:scrollView didScroll:offset percent:percent drag:isDragging];
106 | if (self.isRefreshing) {
107 | if (!isDragging && self.requestFinish) {
108 | [self setRequestFinish:FALSE];
109 | [super finishRefreshWithSuccess:self.isSuccess];
110 | }
111 | }
112 | }
113 |
114 |
115 | //- (void)scrollView:(UIScrollView *)scrollView didChange:(UIRefreshStatus)old status:(UIRefreshStatus)status {
116 | - (void)onStatus:(UIRefreshStatus)old changed:(UIRefreshStatus)status {
117 | // [super scrollView:scrollView didChange:old status:status];
118 | [super onStatus:old changed:status];
119 | if (status == UIRefreshStatusRefreshing) {
120 | [self setScrollMode:UISmartScrollModeFront];
121 | } else if (status == UIRefreshStatusFinish) {
122 | [self setScrollMode:UISmartScrollModeMove];
123 | SKTransition* transition = [SKTransition doorsCloseVerticalWithDuration:0.4];
124 | [self.gameView presentScene:self.sceneStart transition:transition];
125 | } else if (status == UIRefreshStatusIdle) {
126 | [self setNeedsLayout];
127 | }
128 | }
129 |
130 | - (void)onScrollingWithOffset:(CGFloat)offset percent:(CGFloat)percent drag:(BOOL)isDragging {
131 | if (isDragging) {
132 | [self.scenePlaying moveHandle:MIN(1,MAX((percent-1),0))];
133 | }
134 | }
135 |
136 | - (void)finishRefreshWithSuccess:(BOOL)success {
137 | [self.scenePlaying finishWithSuccess: success];
138 | if (self.scrollView.isDragging) {
139 | [self setIsSuccess:success];
140 | [self setRequestFinish:TRUE];
141 | } else {
142 | [super finishRefreshWithSuccess:success];
143 | }
144 | }
145 |
146 | - (void)onStartAnimationWhenRefreshing {
147 | SKTransition* transition = [SKTransition doorsOpenVerticalWithDuration:0.4];
148 | [self.scenePlaying reset];
149 | [self.scenePlaying start];
150 | [self.scenePlaying setTitle:@"Refreshing..."];
151 | [self.gameView presentScene:self.scenePlaying transition:transition];
152 | }
153 |
154 | @end
155 |
--------------------------------------------------------------------------------
/SmartRefreshDemo/Demo/DemoViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DemoViewController.swift
3 | // SmartRefreshDemo
4 | //
5 | // Created by SCWANG on 2021/11/4.
6 | //
7 |
8 | import UIKit
9 |
10 | class DemoViewController: UITableViewController {
11 |
12 |
13 | class Item {
14 | let name: String
15 | let type: UIViewController.Type
16 | init(_ type: UIViewController.Type) {
17 | self.type = type
18 | self.name = NSStringFromClass(type)
19 | .replacingOccurrences(of: "SmartRefreshDemo.UI", with: "")
20 | .replacingOccurrences(of: "Controller", with: "")
21 | }
22 | }
23 |
24 | class Group {
25 | let title: String
26 | let items: [Item]
27 | init(_ title: String, _ items: [Item]) {
28 | self.title = title;
29 | self.items = items;
30 | }
31 | }
32 |
33 | let groups: [Group] = [
34 | Group("Header Style", [
35 | Item(UIDeliveryController.self),
36 | Item(UIClassicsController.self),
37 | Item(UIDropBoxController.self),
38 | Item(UIPhoenixController.self),
39 | Item(UITaurusController.self),
40 | Item(UIBezierRadarController.self),
41 | Item(UIBezierCircleController.self),
42 | Item(UIStoreHouseController.self),
43 | Item(UIWaveSwipeController.self),
44 | Item(UIOriginalController.self),
45 | Item(UIMaterialController.self),
46 | Item(UIGameHitBlockController.self),
47 | Item(UIGameBattleCityController.self),
48 | Item(UIFlyController.self),
49 | ]),
50 | Group("Footer Style", [
51 | Item(UIClassicsFooterController.self),
52 | ])
53 | ]
54 |
55 | override func viewDidLoad() {
56 | super.viewDidLoad()
57 |
58 | // self.navigationItem.backButtonTitle
59 | // Uncomment the following line to preserve selection between presentations
60 | // self.clearsSelectionOnViewWillAppear = false
61 |
62 | // Uncomment the following line to display an Edit button in the navigation bar for this view controller.
63 | // self.navigationItem.rightBarButtonItem = self.editButtonItem
64 | }
65 |
66 | // MARK: - Table view data source
67 |
68 | override func numberOfSections(in tableView: UITableView) -> Int {
69 | // #warning Incomplete implementation, return the number of sections
70 | return self.groups.count
71 | }
72 |
73 | override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
74 | return self.groups[section].title;
75 | }
76 |
77 | override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
78 | // #warning Incomplete implementation, return the number of rows
79 | return self.groups[section].items.count
80 | }
81 |
82 |
83 | override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
84 | let cell = tableView.dequeueReusableCell(withIdentifier: "item", for: indexPath)
85 |
86 | cell.textLabel?.text = self.groups[indexPath.section].items[indexPath.row].name;
87 |
88 | return cell
89 | }
90 |
91 | override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
92 | tableView.deselectRow(at: indexPath, animated: true);
93 | let item = self.groups[indexPath.section].items[indexPath.row];
94 | // self.showDetailViewController(vc, sender: nil);
95 | if item.name == "Delivery" {
96 | self.performSegue(withIdentifier: "segue-delivery", sender: nil);
97 | } else {
98 | let vc = item.type.init()
99 | self.navigationController?.pushViewController(vc, animated: true);
100 | }
101 | }
102 |
103 | /*
104 | // Override to support conditional editing of the table view.
105 | override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
106 | // Return false if you do not want the specified item to be editable.
107 | return true
108 | }
109 | */
110 |
111 | /*
112 | // Override to support editing the table view.
113 | override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
114 | if editingStyle == .delete {
115 | // Delete the row from the data source
116 | tableView.deleteRows(at: [indexPath], with: .fade)
117 | } else if editingStyle == .insert {
118 | // Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view
119 | }
120 | }
121 | */
122 |
123 | /*
124 | // Override to support rearranging the table view.
125 | override func tableView(_ tableView: UITableView, moveRowAt fromIndexPath: IndexPath, to: IndexPath) {
126 |
127 | }
128 | */
129 |
130 | /*
131 | // Override to support conditional rearranging of the table view.
132 | override func tableView(_ tableView: UITableView, canMoveRowAt indexPath: IndexPath) -> Bool {
133 | // Return false if you do not want the item to be re-orderable.
134 | return true
135 | }
136 | */
137 |
138 | /*
139 | // MARK: - Navigation
140 |
141 | // In a storyboard-based application, you will often want to do a little preparation before navigation
142 | override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
143 | // Get the new view controller using segue.destination.
144 | // Pass the selected object to the new view controller.
145 | }
146 | */
147 |
148 | }
149 |
--------------------------------------------------------------------------------
/SmartRefreshControl.podspec:
--------------------------------------------------------------------------------
1 | #
2 | # Be sure to run `pod spec lint SmartRefreshControl.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 https://guides.cocoapods.org/syntax/podspec.html
6 | # To see working Podspecs in the CocoaPods repo see https://github.com/CocoaPods/Specs/
7 | #
8 |
9 | Pod::Spec.new do |spec|
10 |
11 | # ――― Spec Metadata ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――― #
12 | #
13 | # These will help people to find your library, and whilst it
14 | # can feel like a chore to fill in it's definitely to your advantage. The
15 | # summary should be tweet-length, and the description more in depth.
16 | #
17 |
18 | spec.name = "SmartRefreshControl"
19 | spec.version = "0.1.0"
20 | spec.summary = "SmartRefresh for IOS"
21 |
22 | # This description is used to generate tags and improve search results.
23 | # * Think: What does it do? Why did you write it? What is the focus?
24 | # * Try to keep it short, snappy and to the point.
25 | # * Write the description between the DESC delimiters below.
26 | # * Finally, don't worry about the indent, CocoaPods strips it!
27 | spec.description = "A refresh control for ios like the SmartRefreshLayout for android."
28 |
29 | spec.homepage = "https://github.com/scwang90/SmartRefreshControl"
30 | # spec.homepage = "https://gitee.com/scwang90/SmartRefreshControl"
31 | # spec.screenshots = "www.example.com/screenshots_1.gif", "www.example.com/screenshots_2.gif"
32 |
33 |
34 | # ――― Spec License ――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― #
35 | #
36 | # Licensing your code is important. See https://choosealicense.com for more info.
37 | # CocoaPods will detect a license file if there is a named LICENSE*
38 | # Popular ones are 'MIT', 'BSD' and 'Apache License, Version 2.0'.
39 | #
40 |
41 |
42 |
43 | # spec.license = "MIT (example)"
44 | spec.license = { :type => "MIT", :file => "LICENSE" }
45 |
46 |
47 | # ――― Author Metadata ――――――――――――――――――――――――――――――――――――――――――――――――――――――――― #
48 | #
49 | # Specify the authors of the library, with email addresses. Email addresses
50 | # of the authors are extracted from the SCM log. E.g. $ git log. CocoaPods also
51 | # accepts just a name if you'd rather not provide an email address.
52 | #
53 | # Specify a social_media_url where others can refer to, for example a twitter
54 | # profile URL.
55 | #
56 |
57 | spec.author = { "scwang" => "scwang90@hotmail.com" }
58 | # Or just: spec.author = "scwang"
59 | # spec.authors = { "scwang" => "scwang90@hotmail.com" }
60 | # spec.social_media_url = "https://twitter.com/scwang"
61 |
62 | # ――― Platform Specifics ――――――――――――――――――――――――――――――――――――――――――――――――――――――― #
63 | #
64 | # If this Pod runs only on iOS or OS X, then specify the platform and
65 | # the deployment target. You can optionally include the target after the platform.
66 | #
67 |
68 | # spec.platform = :ios
69 | spec.platform = :ios, "10.0"
70 |
71 | # When using multiple platforms
72 | # spec.ios.deployment_target = "5.0"
73 | # spec.osx.deployment_target = "10.7"
74 | # spec.watchos.deployment_target = "2.0"
75 | # spec.tvos.deployment_target = "9.0"
76 |
77 |
78 | # ――― Source Location ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――― #
79 | #
80 | # Specify the location from where the source should be retrieved.
81 | # Supports git, hg, bzr, svn and HTTP.
82 | #
83 |
84 | # spec.source = { :git => "https://github.com/scwang90/SmartRefreshControl.git", :tag => "#{spec.version}" }
85 | spec.source = { :git => "https://gitee.com/scwang90/SmartRefreshControl.git", :tag => "#{spec.version}" }
86 |
87 | # ――― Source Code ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― #
88 | #
89 | # CocoaPods is smart about how it includes source code. For source files
90 | # giving a folder will include any swift, h, m, mm, c & cpp files.
91 | # For header files it will include any header in the folder.
92 | # Not including the public_header_files will make all headers public.
93 | #
94 |
95 | # spec.source_files = "SmartRefreshControl", "SmartRefreshControl/**/*.{h,m}"
96 | spec.source_files = "SmartRefreshControl", "SmartRefreshControl/**/*.{h,m}"
97 | # spec.exclude_files = "SmartRefreshControl/Vector/Refresh-Bridging-Header.h"
98 |
99 | spec.public_header_files = "SmartRefreshControl/*.h", "SmartRefreshControl/**/*.h"
100 | # "SmartRefreshControl/*.h",
101 |
102 | # ――― Resources ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― #
103 | #
104 | # A list of resources included with the Pod. These are copied into the
105 | # target bundle with a build phase script. Anything else will be cleaned.
106 | # You can preserve files from being cleaned, please don't preserve
107 | # non-essential files like tests, examples and documentation.
108 | #
109 |
110 | # spec.resource = "icon.png"
111 | # spec.resources = "Resources/*.png"
112 |
113 | # spec.preserve_paths = "FilesToSave", "MoreFilesToSave"
114 |
115 |
116 | # ――― Project Linking ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――― #
117 | #
118 | # Link your library with frameworks, or libraries. Libraries do not include
119 | # the lib prefix of their name.
120 | #
121 |
122 | # spec.framework = "SomeFramework"
123 | # spec.frameworks = "SomeFramework", "AnotherFramework"
124 |
125 | # spec.library = "iconv"
126 | # spec.libraries = "iconv", "xml2"
127 |
128 |
129 | # ――― Project Settings ――――――――――――――――――――――――――――――――――――――――――――――――――――――――― #
130 | #
131 | # If your library depends on compiler flags you can set them in the xcconfig hash
132 | # where they will only apply to your library. If you depend on other Podspecs
133 | # you can include multiple dependencies to ensure it works.
134 |
135 | spec.requires_arc = true
136 |
137 | # spec.xcconfig = { "HEADER_SEARCH_PATHS" => "$(SDKROOT)/usr/include/libxml2" }
138 | # spec.dependency "JSONKit", "~> 1.4"
139 |
140 | end
141 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # IOS 智能下拉刷新框架 - SmartRefreshControl
2 |
3 | [](https://www.apache.org/licenses/LICENSE-2.0)
4 | [](https://www.cocoapods.org/)
5 | [](https://developer.apple.com/)
6 | [](https://github.com/scwang90)
7 |
8 |
9 |
10 | SmartRefreshControl 是 [SmartRefreshLayout](https://github.com/scwang90/SmartRefreshLayout) 的IOS版,和Android版在 `理念` 和 `外观` 上面保留相同的设计,但是由于 Android 和 IOS 两个系统的差别,IOS版本在功能使用和特性上与安卓版有所差别。刷新控件使用 ObjectiveC 语言编写,演示 DemoApp 使用 Swift 语言编写。
11 |
12 | 目前 `SmartRefreshControl` 功能还不是很强大,也不太稳定,只是在界面层面实现了安卓版的功能。欢迎大家来体验与发现BUG,不推荐使用在正式项目中。
13 |
14 |
15 | ## 由来
16 |
17 | 大学毕业后我大部分时间从事安卓开发,在安卓版 `SmartRefresh` 大火之后,我开始转型 IOS 开发。到现在已经有三年的IOS开发经验,由于IOS上也还未有像 `SmartRefresh` 一样同一个开源库多种外观样式的刷新库,也想巩固自己所学的 IOS技能,我决定在闲暇之余把安卓 `SmartRefresh` 复刻到IOS平台来。经过一年多的努力总算初步完成了。
18 |
19 |
35 |
36 |
44 |
45 |
49 |
50 |
51 | #### 成品展示
52 | |Delivery|Material|
53 | |:---:|:---:|
54 | |||
55 | |[Refresh-your-delivery](https://dribbble.com/shots/2753803-Refresh-your-delivery)|[MaterialHeader](https://developer.android.com/reference/android/support/v4/widget/SwipeRefreshLayout.html)|
56 |
57 | |BezierRadar|BezierCircle|
58 | |:---:|:---:|
59 | |||
60 | |[Pull To Refresh](https://dribbble.com/shots/1936194-Pull-To-Refresh)|[Pull Down To Refresh](https://dribbble.com/shots/1797373-Pull-Down-To-Refresh)|
61 |
62 | |FlyRefresh|DropBox|
63 | |:---:|:---:|
64 | |||
65 | |[FlyRefresh](https://github.com/race604/FlyRefresh)|[DropBoxHeader](#1)|
66 |
67 | |Phoenix|Taurus|
68 | |:---:|:---:|
69 | |||
70 | |[Yalantis/Phoenix](https://github.com/Yalantis/Phoenix)|[Yalantis/Taurus](https://github.com/Yalantis/Taurus)
71 |
72 | |BattleCity|HitBlock|
73 | |:---:|:---:|
74 | |||
75 | |[FunGame/BattleCity](https://github.com/Hitomis/FunGameRefresh)|[FunGame/HitBlock](https://github.com/Hitomis/FunGameRefresh)
76 |
77 |
78 | |StoreHouse|WaveSwipe|
79 | |:---:|:---:|
80 | |||
81 | |[CRefreshLayout](https://github.com/cloay/CRefreshLayout)|[WaveSwipeRefreshLayout](https://github.com/recruit-lifestyle/WaveSwipeRefreshLayout)
82 |
83 |
84 | |Original|Classics|
85 | |:---:|:---:|
86 | |||
87 | |[FlyRefresh](https://github.com/race604/FlyRefresh)|[ClassicsHeader](#1)|
88 |
89 |
90 |
91 | ## 简单用例
92 |
93 | #### 1.在 `Podfile` 中添加依赖
94 |
95 |
96 | ```
97 |
98 | pod 'SmartRefreshControl', '~> 0.1.0'
99 |
100 | ```
101 |
102 | #### 2.在 `ViewController` 中添加刷新头
103 |
104 | ```ObjectiveC
105 |
106 | #import
107 |
108 | @interface DemoTableViewController ()
109 |
110 | @property (strong, nonatomic) IBOutlet UITableView *tableView;
111 | @property (strong, nonatomic) UIRefreshBezierRadarHeader *header;
112 |
113 | @end
114 |
115 | @implementation DemoTableViewController
116 |
117 | - (void)viewDidLoad {
118 | [super viewDidLoad];
119 |
120 | //方式1: 初始化同时绑定事件
121 | [self setHeader:[UIRefreshBezierRadarHeader attach:self.tableView target:self action:@selector(onRefresh)]];
122 |
123 | //方式2: 先初始化,再绑定事件
124 | [self setHeader:[UIRefreshBezierRadarHeader attach:self.tableView]];
125 | [self.header addTarget:self action:@selector(onRefresh)];
126 |
127 | //方式3: 先创建,再绑定
128 | [self setHeader:[UIRefreshBezierRadarHeader new]];
129 | [self.header attach:self.tableView];
130 | [self.header addTarget:self action:@selector(onRefresh)];
131 |
132 | }
133 |
134 | @end
135 |
136 | ```
137 |
138 | #### 3.添加刷新监听事件
139 |
140 | ```ObjectiveC
141 |
142 | @implementation DemoTableViewController
143 |
144 | - (void)onRefresh {
145 | [self.header finishRefresh]; //关闭刷新,可以改成请求网络,成功/失败之后再关闭刷新
146 | }
147 |
148 | @end
149 |
150 | ```
151 |
152 | License
153 | -------
154 |
155 | MIT License
156 |
157 | Copyright (c) 2021 树朾
158 |
159 | Permission is hereby granted, free of charge, to any person obtaining a copy
160 | of this software and associated documentation files (the "Software"), to deal
161 | in the Software without restriction, including without limitation the rights
162 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
163 | copies of the Software, and to permit persons to whom the Software is
164 | furnished to do so, subject to the following conditions:
165 |
166 | The above copyright notice and this permission notice shall be included in all
167 | copies or substantial portions of the Software.
168 |
169 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
170 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
171 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
172 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
173 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
174 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
175 | SOFTWARE.
--------------------------------------------------------------------------------
/SmartRefreshControl/Footer/UIRefreshClassicsFooter.m:
--------------------------------------------------------------------------------
1 | //
2 | // UIRefreshClassicsFooter.m
3 | // Refresh
4 | //
5 | // Created by SCWANG on 2021/7/5.
6 | // Copyright © 2021 Teeyun. All rights reserved.
7 | //
8 |
9 | #import "UIRefreshClassicsFooter.h"
10 |
11 | @interface UIRefreshClassicsFooter ()
12 |
13 | @property (nonatomic, strong) UILabel *labelTitle;
14 | @property (nonatomic, strong) UIImageView *viewArrow;
15 | @property (nonatomic, strong) UIActivityIndicatorView *viewLoading;
16 |
17 | @end
18 |
19 | @implementation UIRefreshClassicsFooter
20 |
21 | - (void)setUpComponent {
22 | [super setUpComponent];
23 | [self setIsAutoLoadMore:TRUE];
24 |
25 | [self setHeight:50];
26 | [self setSpaceOfLoadingAndText:10];
27 | [self setScrollMode:UISmartScrollModeMove];
28 |
29 | [self addSubview:self.labelTitle];
30 | [self addSubview:self.viewArrow];
31 | [self addSubview:self.viewLoading];
32 | }
33 |
34 | - (void)layoutSubviews {
35 | [super layoutSubviews];
36 |
37 | CGSize sizeHeader = self.frame.size;
38 | CGFloat centerArrowX = sizeHeader.width / 2;
39 | CGFloat centerArrowY = sizeHeader.height / 2;
40 |
41 | if (!self.labelTitle.isHidden) {
42 | CGSize sizeTitle = [self.labelTitle sizeThatFits:self.bounds.size];
43 | CGRect rectTitle = CGRectMake(0, 0, sizeTitle.width, sizeTitle.height);
44 | CGFloat textWidth = sizeTitle.width;
45 |
46 | rectTitle.origin.x = (sizeHeader.width - sizeTitle.width) / 2;
47 |
48 | rectTitle.origin.y = (sizeHeader.height - sizeTitle.height) / 2;
49 |
50 | centerArrowX -= textWidth / 2 + self.spaceOfLoadingAndText;
51 |
52 | self.labelTitle.frame = rectTitle;
53 | }
54 |
55 | CGSize sizeLoading = self.viewLoading.frame.size;
56 | CGSize sizeArrow = self.viewArrow.image ? self.viewArrow.image.size : CGSizeZero;
57 | CGPoint originArrow = CGPointMake(centerArrowX - sizeArrow.width / 2, centerArrowY - sizeArrow.height / 2);
58 | self.viewArrow.frame = CGRectMake(originArrow.x, originArrow.y, sizeArrow.width, sizeArrow.height);
59 | self.viewArrow.center = CGPointMake(centerArrowX - sizeArrow.width / 2, centerArrowY);
60 | self.viewLoading.center = CGPointMake(centerArrowX - sizeLoading.width / 2, centerArrowY);
61 | }
62 |
63 | - (void)setColorAccent:(UIColor *)colorAccent {
64 | [super setColorAccent:colorAccent];
65 | self.labelTitle.textColor = colorAccent;
66 | self.viewLoading.color = colorAccent;
67 | self.viewArrow.tintColor = colorAccent;
68 | }
69 |
70 | - (void)setColorPrimary:(UIColor *)colorPrimary {
71 | [super setColorPrimary:colorPrimary];
72 | self.backgroundColor = colorPrimary;
73 | }
74 |
75 | #pragma mark - Lazy
76 |
77 | - (UILabel *)labelTitle {
78 | if (_labelTitle == nil) {
79 | _labelTitle = [UILabel new];
80 | _labelTitle.font = [UIFont systemFontOfSize:15];
81 | }
82 | return _labelTitle;
83 | }
84 |
85 | - (UIImageView *)viewArrow {
86 | if (_viewArrow == nil) {
87 | _viewArrow = [UIImageView new];
88 |
89 | CGFloat width = 18,height = 18;
90 | CGFloat lineWidth = width * 25 / 225;
91 | CGFloat vector1 = (lineWidth * 0.70710678118654752440084436210485f);//Math.sin(Math.PI/4));
92 | CGFloat vector2 = (lineWidth / 0.70710678118654752440084436210485f);//Math.sin(Math.PI/4));
93 |
94 | CGFloat scale = [UIScreen mainScreen].scale;
95 |
96 | UIGraphicsBeginImageContextWithOptions(CGSizeMake(width, height), NO, scale);
97 | CGContextRef context = UIGraphicsGetCurrentContext();
98 |
99 | CGContextMoveToPoint(context, width/2, height);
100 | CGContextAddLineToPoint(context, 0, height / 2);
101 | CGContextAddLineToPoint(context, vector1, height / 2 - vector1);
102 | CGContextAddLineToPoint(context, width / 2 - lineWidth / 2, height - vector2 - lineWidth / 2);
103 | CGContextAddLineToPoint(context, width / 2 - lineWidth / 2, 0);
104 | CGContextAddLineToPoint(context, width / 2 + lineWidth / 2, 0);
105 | CGContextAddLineToPoint(context, width / 2 + lineWidth / 2, height - vector2 - lineWidth / 2);
106 | CGContextAddLineToPoint(context, width - vector1, height / 2 - vector1);
107 | CGContextAddLineToPoint(context, width, height / 2);
108 | CGContextClosePath(context);
109 |
110 | CGContextFillPath(context);
111 | UIImage *img = UIGraphicsGetImageFromCurrentImageContext();
112 | UIGraphicsEndImageContext();
113 |
114 | _viewArrow.image = [img imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
115 | }
116 | return _viewArrow;
117 | }
118 |
119 | - (UIActivityIndicatorView *)viewLoading {
120 | if (_viewLoading == nil) {
121 | NSInteger style = 2;//UIActivityIndicatorViewStyleGray;
122 | _viewLoading = [UIActivityIndicatorView alloc];
123 | _viewLoading = [_viewLoading initWithActivityIndicatorStyle:style];
124 | _viewLoading.hidesWhenStopped = TRUE;
125 | }
126 | return _viewLoading;
127 | }
128 |
129 | #pragma mark - Override
130 |
131 |
132 | - (void)onStatus:(UISmartFooterStatus)old changed:(UISmartFooterStatus)status {
133 | [super onStatus:old changed:status];
134 |
135 | [self setNeedsLayout];
136 |
137 | switch (status) {
138 | case UISmartFooterStatusWillLoadMore:
139 | case UISmartFooterStatusLoading:
140 | self.labelTitle.text = @"正在加载...";
141 | break;
142 | case UISmartFooterStatusReleaseToLoadMore:
143 | self.labelTitle.text = @"释放立即加载";
144 | break;
145 | case UISmartFooterStatusNoMoreData:
146 | self.labelTitle.text = @"没有更多数据了";
147 | break;
148 | default:
149 | if (self.isAutoLoadMore) {
150 | self.labelTitle.text = @"点击加载更多";
151 | } else {
152 | self.labelTitle.text = @"下拉开始加载";
153 | }
154 | break;
155 | }
156 |
157 | if (status == UISmartFooterStatusReleaseToLoadMore) {
158 | [UIView animateWithDuration:self.durationNormal animations:^{
159 | self.viewArrow.transform = CGAffineTransformMakeRotation(M_PI);
160 | }];
161 | } else if (old == UISmartFooterStatusReleaseToLoadMore) {
162 | [UIView animateWithDuration:self.durationNormal animations:^{
163 | self.viewArrow.transform = CGAffineTransformIdentity;
164 | }];
165 | }
166 |
167 | if (status == UISmartFooterStatusLoading) {
168 | self.viewArrow.hidden = true;
169 | [self.viewLoading startAnimating];
170 | } else if (old == UISmartFooterStatusLoading) {
171 | self.viewArrow.hidden = false;
172 | [self.viewLoading stopAnimating];
173 | }
174 |
175 | if (self.isAutoLoadMore) {
176 | self.viewArrow.hidden = true;
177 | }
178 | }
179 |
180 | /*
181 | // Only override drawRect: if you perform custom drawing.
182 | // An empty implementation adversely affects performance during animation.
183 | - (void)drawRect:(CGRect)rect {
184 | // Drawing code
185 | }
186 | */
187 |
188 |
189 | @end
190 |
--------------------------------------------------------------------------------
/SmartRefreshControl/Vector/ValueAnimator.m:
--------------------------------------------------------------------------------
1 | //
2 | // ValueAnimator.m
3 | // Refresh
4 | //
5 | // Created by Teeyun on 2020/9/14.
6 | // Copyright © 2020 Teeyun. All rights reserved.
7 | //
8 |
9 | #import "ValueAnimator.h"
10 |
11 | @interface ValueAnimator ()
12 |
13 | @property (nonatomic, weak) id target;
14 | @property (nonatomic, assign) SEL action;
15 |
16 | @property (nonatomic, assign) BOOL reversed;
17 | @property (nonatomic, assign) CGFloat value;
18 | @property (nonatomic, strong) CADisplayLink *link;
19 | @property (nonatomic, assign) CFTimeInterval timeStart;
20 | @property (nonatomic, assign) CFTimeInterval timeOffset;
21 |
22 | @property (nonatomic, copy) NSArray *points;
23 |
24 | @end
25 |
26 | @implementation ValueAnimator
27 |
28 | + (ValueAnimator *)newWithFrom:(CGFloat)from to:(CGFloat)to {
29 | ValueAnimator* this = [self new];
30 | if (this) {
31 | [this setFrom:from to:to];
32 | }
33 | return this;
34 | }
35 |
36 | + (ValueAnimator *)newWithTarget:(id)target selector:(SEL)action {
37 | ValueAnimator* this = [self new];
38 | if (this) {
39 | [this setTarget:target selector:action];
40 | }
41 | return this;
42 | }
43 |
44 | - (instancetype)init
45 | {
46 | self = [super init];
47 | if (self) {
48 | [self initialize];
49 | }
50 | return self;
51 | }
52 |
53 | - (void)dealloc
54 | {
55 | [self stop];
56 | }
57 |
58 | - (void)setTarget:(id)target selector:(SEL)action {
59 | self.target = target;
60 | self.action = action;
61 | }
62 |
63 | - (void)setFrom:(CGFloat)from to:(CGFloat)to {
64 | self.toValue = to;
65 | self.fromValue = from;
66 |
67 | if (!self.isRunning) {
68 | self.value = from;
69 | }
70 | }
71 |
72 | - (void)setFromToPoints:(NSArray *)points {
73 | self.points = points;
74 |
75 | if (!self.isRunning && points.count > 0) {
76 | self.value = points[0].floatValue;
77 | }
78 | }
79 |
80 | - (void)initialize {
81 | self.speed = 1;
82 | self.duration = 1;
83 | self.repeatCount = 1;
84 | self.fromValue = 0;
85 | self.toValue = 1;
86 | self.beginTime = 0;
87 | }
88 |
89 | - (BOOL)isRunning {
90 | return self.link != nil;
91 | }
92 |
93 | - (void)startInternal {
94 | self.timeOffset = 0;
95 | self.reversed = false;
96 | self.timeStart = CACurrentMediaTime();
97 | self.link = [CADisplayLink displayLinkWithTarget:self selector:@selector(step:)];
98 | [self.link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
99 | [self.link addToRunLoop:[NSRunLoop mainRunLoop] forMode:UITrackingRunLoopMode];
100 |
101 | if (self.delegate && [self.delegate respondsToSelector:@selector(onAnimationStart:)]) {
102 | [self.delegate onAnimationStart:self];
103 | }
104 | }
105 |
106 | - (void)start {
107 | if (!self.link) {
108 | if (self.beginTime > 0) {
109 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(_beginTime * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
110 | [self startInternal];
111 | });
112 | } else {
113 | [self startInternal];
114 | }
115 | }
116 | }
117 |
118 | - (void)stop {
119 | if (self.link) {
120 | [self.link invalidate];
121 | self.link = nil;
122 | }
123 | }
124 |
125 | - (CGFloat)valueFromPoints:(NSArray*) points time:(CGFloat)time reverse:(BOOL) reversed {
126 | NSEnumerator *enumer = nil;
127 | if (reversed) {
128 | enumer = [points reverseObjectEnumerator];
129 | } else {
130 | enumer = [points objectEnumerator];
131 | }
132 |
133 | for (NSUInteger count = points.count,i = 0; i < count - 1 && count > 1; i++) {
134 | CGFloat from = [[enumer nextObject] floatValue];
135 | CGFloat start = 1.0 * i / count;
136 | CGFloat end = 1.0 * (i + 1) / count;
137 | if (time >= start && time < end) {
138 | CGFloat to = [[enumer nextObject] floatValue];
139 | time = (time - start) / (end - start);
140 | return from + (to - from) * time;
141 | }
142 | }
143 |
144 | return [points[0] floatValue];
145 | }
146 |
147 | - (CGFloat)percent {
148 | return [self interpolation:self.timeOffset / self.duration];
149 | }
150 |
151 | - (void)step:(CADisplayLink*) link {
152 | CFTimeInterval thisStep = CACurrentMediaTime();
153 | CFTimeInterval stepDuration = thisStep - self.timeStart;
154 | //update time offset
155 | self.timeOffset = MIN(stepDuration, self.duration);
156 | //get normalized time offset (in range 0 - 1)
157 | CGFloat time = [self interpolation:self.timeOffset / self.duration];
158 |
159 |
160 | if (self.points.count) {
161 | self.value = [self valueFromPoints:self.points time:time reverse:self.reversed];
162 | } else {
163 | CGFloat from = self.reversed ? self.toValue : self.fromValue;
164 | CGFloat to = self.reversed ? self.fromValue : self.toValue;
165 | self.value = from + (to - from) * time;
166 | }
167 |
168 |
169 | if (self.target) {
170 | #pragma clang diagnostic push
171 | #pragma clang diagnostic ignored "-Warc-performSelector-leaks"
172 | [self.target performSelector:self.action withObject:self];
173 | #pragma clang diagnostic pop
174 | }
175 |
176 | if (self.timeOffset >= self.duration) {
177 | if (self.repeatCount != INFINITY) {
178 | self.repeatCount = self.repeatCount - 1;
179 | }
180 | if (self.repeatCount <= 0) {
181 | if (self.delegate && [self.delegate respondsToSelector:@selector(onAnimationEnd:)]) {
182 | [self.delegate onAnimationEnd:self];
183 | }
184 | [self stop];
185 | } else {
186 | if (self.delegate && [self.delegate respondsToSelector:@selector(onAnimationRepeat:)]) {
187 | [self.delegate onAnimationRepeat:self];
188 | }
189 | if (self.repeatMode == AnimatorRepeatModeReverse) {
190 | self.reversed = !self.reversed;
191 | } else {
192 | self.value = self.fromValue;
193 | }
194 | }
195 | self.timeStart = thisStep;
196 | }
197 | }
198 |
199 | - (CGFloat) interpolation:(CGFloat) value {
200 | if (self.interpolatorBlock) {
201 | return _interpolatorBlock(value);
202 | }
203 | if (_interpolator == AnimatorInterpolatorAccelerateDecelerate) {
204 | return cos((value + 1)*3.1415926) / 2 + 0.5;
205 | } else if (_interpolator == AnimatorInterpolatorAccelerate) {
206 | return value * value;
207 | } else if (_interpolator == AnimatorInterpolatorDecelerate) {
208 | return (1.0 - (1.0 - value) * (1.0 - value));
209 | } else if (_interpolator == AnimatorInterpolatorBounce) {
210 | #define bounce(v) (v)*(v)*8
211 | value *= 1.1226f;
212 | if (value < 0.3535f) return bounce(value);
213 | else if (value < 0.7408f) return bounce(value - 0.54719f) + 0.7f;
214 | else if (value < 0.9644f) return bounce(value - 0.8526f) + 0.9f;
215 | else return bounce(value - 1.0435f) + 0.95f;
216 | }
217 | return value;
218 | }
219 |
220 | @end
221 |
--------------------------------------------------------------------------------
/SmartRefreshControl/Header/UIRefreshGameHitBlockHeader.m:
--------------------------------------------------------------------------------
1 | //
2 | // UIRefreshGameHitBlockHeader.m
3 | // Refresh
4 | //
5 | // Created by SCWANG on 2021/6/6.
6 | // Copyright © 2021 Teeyun. All rights reserved.
7 | //
8 |
9 | #import "UIRefreshGameHitBlockHeader.h"
10 |
11 | #import "UIRefreshGamePlayingScene.h"
12 |
13 | #define NAME_BALL @"ball"
14 | #define NAME_BLOCK @"block"
15 | #define NAME_LINE @"line"
16 | #define NAME_PADDLE @"paddle"
17 |
18 | #define MASK_BALL 0x1
19 | #define MASK_LINE 0x2
20 | #define MASK_BLOCK 0x4
21 | #define MASK_PADDLE 0x8
22 |
23 | @interface HitBlockScene : UIRefreshGamePlayingScene
24 |
25 | @property (nonatomic, strong) SKNode *nodeEndLine;
26 | @property (nonatomic, strong) SKSpriteNode *nodeBall;
27 | @property (nonatomic, strong) SKSpriteNode *nodePaddle;
28 |
29 | @end
30 |
31 | @interface UIRefreshGameHeader (UIRefreshGameHitBlockHeader)
32 |
33 | - (UIRefreshGamePlayingScene*) newPlayingScene;
34 |
35 | @end
36 |
37 | @implementation UIRefreshGameHitBlockHeader
38 |
39 | - (UIRefreshGamePlayingScene *)newPlayingScene {
40 | return [HitBlockScene new];
41 | }
42 |
43 | /*
44 | // Only override drawRect: if you perform custom drawing.
45 | // An empty implementation adversely affects performance during animation.
46 | - (void)drawRect:(CGRect)rect {
47 | // Drawing code
48 | }
49 | */
50 |
51 | @end
52 |
53 | @implementation HitBlockScene
54 |
55 | - (instancetype)init
56 | {
57 | self = [super init];
58 | if (self) {
59 | [self initize];
60 | }
61 | return self;
62 | }
63 |
64 | - (void)dealloc
65 | {
66 | NSLog(@"dealloc-%@", self.name);
67 | }
68 |
69 | - (void)initize {
70 | [self setName:@"HitBlockScene"];
71 | [self setScaleMode:SKSceneScaleModeFill];
72 | [self.physicsWorld setGravity:CGVectorMake(0, 0)];
73 | [self.physicsWorld setContactDelegate:self];
74 |
75 | [self setNodeEndLine:[SKNode new]];
76 | [self.nodeEndLine setName:NAME_LINE];
77 | [self addChild:self.nodeEndLine];
78 |
79 | CGSize sizePaddle = CGSizeMake(5, 30);
80 | [self setNodePaddle:[SKSpriteNode new]];
81 | [self.nodePaddle setSize:sizePaddle];
82 | [self.nodePaddle setName:NAME_PADDLE];
83 | [self.nodePaddle setPhysicsBody:[SKPhysicsBody bodyWithRectangleOfSize:sizePaddle]];
84 | [self.nodePaddle.physicsBody setDynamic:FALSE];
85 | [self.nodePaddle.physicsBody setFriction:0];
86 | [self.nodePaddle.physicsBody setRestitution:1];
87 | [self.nodePaddle.physicsBody setCategoryBitMask:MASK_PADDLE];
88 | [self addChild:self.nodePaddle];
89 |
90 | CGSize sizeBall = CGSizeMake(8, 8);
91 | [self setNodeBall:[SKSpriteNode new]];
92 | [self.nodeBall setSize:sizeBall];
93 | [self.nodeBall setName:NAME_BALL];
94 | [self.nodeBall setPhysicsBody:[SKPhysicsBody bodyWithCircleOfRadius:sizeBall.width/2]];
95 | [self.nodeBall.physicsBody setFriction:0];
96 | [self.nodeBall.physicsBody setRestitution:1];
97 | [self.nodeBall.physicsBody setLinearDamping:0];
98 | [self.nodeBall.physicsBody setAllowsRotation:FALSE];
99 | [self.nodeBall.physicsBody setUsesPreciseCollisionDetection:TRUE];
100 | [self.nodeBall.physicsBody setCategoryBitMask:MASK_BALL];
101 | [self.nodeBall.physicsBody setContactTestBitMask:MASK_LINE|MASK_BLOCK];
102 | [self addChild:self.nodeBall];
103 |
104 | // SKSpriteNode* node = nil;
105 | //
106 | // NSLog(@"node = %@", NSStringFromCGSize(node.size));
107 | }
108 |
109 | - (void)setColorAccent:(UIColor *)colorAccent {
110 | [super setColorAccent:colorAccent];
111 | [self.nodeBall setColor:colorAccent];
112 | [self.nodePaddle setColor:colorAccent];
113 | }
114 |
115 | - (void)didChangeSize:(CGSize)oldSize {
116 | [super didChangeSize:oldSize];
117 |
118 | CGSize size = self.size;
119 |
120 | id body = [SKPhysicsBody bodyWithEdgeLoopFromRect:self.frame];
121 | [self setPhysicsBody:body];
122 | [self.physicsBody setFriction:0.0];
123 | [self.physicsBody setRestitution:0.1];
124 | [self.physicsBody setDynamic:YES];
125 |
126 | CGPoint from = CGPointMake(size.width - 1, 0);
127 | CGPoint to = CGPointMake(size.width - 1, size.height);
128 | id lineBody = [SKPhysicsBody bodyWithEdgeFromPoint:from toPoint:to];
129 | [self.nodeEndLine setPhysicsBody:lineBody];
130 | [self.nodePaddle.physicsBody setCategoryBitMask:MASK_LINE];
131 |
132 | CGFloat yPaddle = size.height - self.nodePaddle.size.height / 2;
133 | [self.nodePaddle setPosition:CGPointMake(size.width-30, yPaddle)];
134 | }
135 |
136 | - (void)reset {
137 | CGSize size = self.size;
138 | CGSize sizePaddle = self.nodePaddle.size;
139 | [self.nodeBall setHidden:FALSE];
140 | [self.nodeBall setPosition:CGPointMake(size.width-30-sizePaddle.width, size.height/2)];
141 | [self.nodeBall.physicsBody setVelocity:CGVectorMake(0, 0)];
142 |
143 | CGFloat yPaddle = size.height - sizePaddle.height / 2;
144 | [self.nodePaddle setPosition:CGPointMake(size.width-30, yPaddle)];
145 |
146 | for (id node = [self childNodeWithName:NAME_BLOCK]; node != nil; ) {
147 | [node removeFromParent];
148 | node = [self childNodeWithName:NAME_BLOCK];
149 | }
150 |
151 | CGSize sizeBlock = CGSizeMake(5, (self.size.height-4)/5);
152 |
153 | for (int i = 0; i < 3; i++) {
154 | UIColor* color = [self.colorAccent colorWithAlphaComponent:1-i*0.2];
155 | for (int j = 0; j < 5; j++) {
156 | SKPhysicsBody* blockBody = [SKPhysicsBody bodyWithRectangleOfSize:sizeBlock];
157 | [blockBody setFriction:0];
158 | [blockBody setRestitution:1];
159 | [blockBody setDynamic:FALSE];
160 | [blockBody setAllowsRotation:FALSE];
161 | [blockBody setCategoryBitMask:MASK_BLOCK];
162 |
163 | CGFloat y = j*(sizeBlock.height+1)+sizeBlock.height/2;
164 | SKSpriteNode* node = [SKSpriteNode new];
165 | [node setColor:color];
166 | [node setSize:sizeBlock];
167 | [node setName:NAME_BLOCK];
168 | [node setPhysicsBody:blockBody];
169 | [node setPosition:CGPointMake(20+i*6, y)];
170 | [self addChild:node];
171 | }
172 | }
173 | }
174 |
175 | - (void)start {
176 | CGFloat y = (30.0 - (arc4random() % 60)) / 100;
177 | NSLog(@"start.y = %@", @(y));
178 | [self.nodeBall.physicsBody applyImpulse:CGVectorMake(-0.5, y)];
179 | }
180 |
181 | - (void)moveHandle:(CGFloat)percent {
182 | CGSize sizeScene = self.size;
183 | CGSize sizePaddle = self.nodePaddle.size;
184 | CGPoint old = self.nodePaddle.position;
185 | CGFloat y = (sizeScene.height - sizePaddle.height)*(1-percent) + sizePaddle.height/2;
186 | CGPoint new = CGPointMake(old.x, y);
187 | [self.nodePaddle setPosition:new];
188 | }
189 |
190 | //- (void)didBeginContact:(SKPhysicsContact *)contact {
191 | // NSLog(@"didBeginContact=%@", contact);
192 | //}
193 |
194 | - (void)didEndContact:(SKPhysicsContact *)contact {
195 |
196 | NSArray* array = @[contact.bodyA.node, contact.bodyB.node];
197 | for (SKNode* node in array) {
198 | if (node == self.nodeEndLine) {
199 | [self gameOver];
200 | } else if (node.physicsBody.categoryBitMask == MASK_BLOCK) {
201 | [node removeFromParent];
202 | }
203 | }
204 |
205 | if (self.isGameWin) {
206 | [self gameWin];
207 | }
208 | }
209 |
210 | - (void)gameWin {
211 | [super gameWin];
212 | [self.nodeBall.physicsBody setVelocity:CGVectorMake(0, 0)];
213 | [self.nodeBall setHidden:YES];
214 | }
215 |
216 | - (void)gameOver {
217 | [super gameOver];
218 | [self.nodeBall.physicsBody setVelocity:CGVectorMake(0, 0)];
219 | [self.nodeBall setHidden:YES];
220 | }
221 |
222 | - (BOOL)isGameWin {
223 | __block NSUInteger count = 0;
224 | [self enumerateChildNodesWithName:NAME_BLOCK usingBlock:^(SKNode* node, BOOL* stop) {
225 | count = count + 1;
226 | }];
227 | return count == 0;
228 | }
229 |
230 |
231 | @end
232 |
--------------------------------------------------------------------------------
/SmartRefreshControl/Fly/MountainView.m:
--------------------------------------------------------------------------------
1 | //
2 | // MountainView.m
3 | // Refresh
4 | //
5 | // Created by SCWANG on 2021/6/13.
6 | // Copyright © 2021 Teeyun. All rights reserved.
7 | //
8 |
9 | #import "MountainView.h"
10 |
11 | #define ARGB(argb)[UIColor colorWithRed:((float)((argb&0xFF0000)>> 16))/ 255.0 green:((float)((argb&0x00FF00)>> 8))/ 255.0 blue: ((float)(argb&0x0000FF))/ 255.0 alpha:((float)((argb&0xFF000000)>>24))/ 255.0]
12 |
13 | #define TREE_WIDTH 30
14 | #define TREE_HEIGHT 60
15 |
16 | @interface MountainView ()
17 |
18 | @property (nonatomic, strong) UIColor *colorBackground;
19 | @property (nonatomic, strong) UIColor *colorMount1;
20 | @property (nonatomic, strong) UIColor *colorMount2;
21 | @property (nonatomic, strong) UIColor *colorMount3;
22 | @property (nonatomic, strong) UIColor *colorTree1Brink;
23 | @property (nonatomic, strong) UIColor *colorTree1Branch;
24 | @property (nonatomic, strong) UIColor *colorTree2Brink;
25 | @property (nonatomic, strong) UIColor *colorTree2Branch;
26 | @property (nonatomic, strong) UIColor *colorTree3Brink;
27 | @property (nonatomic, strong) UIColor *colorTree3Branch;
28 |
29 | @property (nonatomic, assign) CGFloat offset;
30 | @property (nonatomic, strong) UIBezierPath *treePathBrink;
31 | @property (nonatomic, strong) UIBezierPath *treePathBranch;
32 |
33 | @end
34 |
35 | @implementation MountainView
36 |
37 | #pragma mark - lifecycle
38 |
39 | - (void)setFrame:(CGRect)frame {
40 | [super setFrame:frame];
41 | [self setNeedsDisplay];
42 | }
43 |
44 | - (void)setColorPrimary:(UIColor *)color {
45 | _colorPrimary = color;
46 |
47 | [self setBackgroundColor:color];
48 | [self setColorMount1:[self mix:color c2:ARGB(0x99ffffff)]];
49 | [self setColorMount2:[self mix:color c2:ARGB(0x993C929C)]];
50 | [self setColorMount3:[self mix:color c2:ARGB(0xCC3E5F73)]];
51 |
52 | [self setColorTree1Branch:[self mix:color c2:ARGB(0x551F7177)]];
53 | [self setColorTree1Brink:[self mix:color c2:ARGB(0xCC0C3E48)]];
54 | [self setColorTree2Branch:[self mix:color c2:ARGB(0x5534888F)]];
55 | [self setColorTree2Brink:[self mix:color c2:ARGB(0xCC1B6169)]];
56 | [self setColorTree3Branch:[self mix:color c2:ARGB(0x5557B1AE)]];
57 | [self setColorTree3Brink:[self mix:color c2:ARGB(0xCC62A4AD)]];
58 |
59 | }
60 |
61 | - (UIColor *)mix:(UIColor*)color1 c2:(UIColor *)color2
62 | {
63 | const CGFloat * components1 = CGColorGetComponents(color1.CGColor);
64 | const CGFloat * components2 = CGColorGetComponents(color2.CGColor);
65 | const CGFloat ratio = components2[3];
66 |
67 | CGFloat r = components1[0]*(1-ratio) + components2[0]*ratio;
68 | CGFloat g = components1[1]*(1-ratio) + components2[1]*ratio;
69 | CGFloat b = components1[2]*(1-ratio) + components2[2]*ratio;
70 |
71 | return [UIColor colorWithRed:r green:g blue:b alpha:1];
72 | }
73 |
74 | - (void)drawRect:(CGRect)rect {
75 | [super drawRect:rect];
76 |
77 | CGFloat width = rect.size.width;
78 | CGFloat height = rect.size.height;
79 | CGContextRef context = UIGraphicsGetCurrentContext();
80 | [self drawMountain:context width:width height:height];
81 | }
82 |
83 | - (void)drawMountain:(CGContextRef) context width:(CGFloat)width height:(CGFloat)height {
84 | CGFloat y = 0;
85 | [self.colorMount1 setFill];
86 | CGContextMoveToPoint(context, 0, y = height - width*4/29 - _offset*0.6);
87 | CGContextAddLineToPoint(context, width*6/29, y -= width*4/29);
88 | CGContextAddLineToPoint(context, width*17/29, y += width*4.5/29);
89 | CGContextAddLineToPoint(context, width*27/29, y -= width*5.5/29);
90 | CGContextAddLineToPoint(context, width, y += width*2/29);
91 | CGContextAddLineToPoint(context, width, height);
92 | CGContextAddLineToPoint(context, 0, height);
93 | CGContextClosePath(context);
94 | CGContextFillPath(context);
95 |
96 | CGContextSaveGState(context);
97 | CGContextTranslateCTM(context, width*6.5/29, height - width*5/29 - _offset*0.4);
98 | CGContextScaleCTM(context, -0.7, -0.7);
99 | [self drawTree:context];
100 | CGContextRestoreGState(context);
101 |
102 |
103 | CGContextSaveGState(context);
104 | CGContextTranslateCTM(context, width*4.5/29, height - width*4.5/29 - _offset*0.4);
105 | CGContextScaleCTM(context, -0.5, -0.5);
106 | [self drawTree:context];
107 | CGContextRestoreGState(context);
108 |
109 | [self.colorMount2 setFill];
110 | CGContextMoveToPoint(context, 0, y = height - width*3/29 - _offset*0.4);
111 | CGContextAddLineToPoint(context, width*7/29, y -= width*3/29);
112 | CGContextAddLineToPoint(context, width*18/29, y += width*4.5/29);
113 | CGContextAddLineToPoint(context, width*26/29, y -= width*4.5/29);
114 | CGContextAddLineToPoint(context, width, y += width*2/29);
115 | CGContextAddLineToPoint(context, width, height);
116 | CGContextAddLineToPoint(context, 0, height);
117 | CGContextClosePath(context);
118 | CGContextFillPath(context);
119 |
120 |
121 | CGContextSaveGState(context);
122 | CGContextTranslateCTM(context, width*16/29, height - width*3/29 - _offset*0.2);
123 | CGContextScaleCTM(context, 0.85, -0.85);
124 | [self drawTree:context];
125 | CGContextRestoreGState(context);
126 |
127 | CGContextSaveGState(context);
128 | CGContextTranslateCTM(context, width*19/29, height - width*3/29 - _offset*0.2);
129 | CGContextScaleCTM(context, 1, -1);
130 | [self drawTree:context];
131 | CGContextRestoreGState(context);
132 |
133 |
134 | CGContextSaveGState(context);
135 | CGContextTranslateCTM(context, width*22/29, height - width*3/29 - _offset*0.2);
136 | CGContextScaleCTM(context, 0.7, -0.7);
137 | [self drawTree:context];
138 | CGContextRestoreGState(context);
139 |
140 | [self.colorMount3 setFill];
141 | CGContextMoveToPoint(context, 0, y = height - width*2/29 - _offset*0.2);
142 | CGContextAddCurveToPoint(context, width*3.5/29, y - width*1/29, width*21/29, y - width*3/29, width, y - width*1/29);
143 | CGContextAddLineToPoint(context, width, height);
144 | CGContextAddLineToPoint(context, 0, height);
145 | CGContextClosePath(context);
146 | CGContextFillPath(context);
147 |
148 | }
149 |
150 | - (void)drawTree:(CGContextRef)context {
151 | CGContextSetLineWidth(context, 6);
152 |
153 | CGContextTranslateCTM(context, 0, TREE_HEIGHT / 2);
154 | [self.colorTree1Branch setFill];
155 | [self.treePathBranch fill];
156 | [self.colorTree1Brink setStroke];
157 | [self.treePathBranch stroke];
158 |
159 | CGContextTranslateCTM(context, 0, -TREE_HEIGHT / 2);
160 | [self.colorTree1Brink setFill];
161 | [self.treePathBrink fill];
162 | }
163 |
164 | - (void)onScrollingWithOffset:(CGFloat)offset percent:(CGFloat)percent {
165 | [self setOffset: offset];
166 | [self updateTreePath: percent];
167 | }
168 |
169 | - (void)updateTreePath:(CGFloat)percent {
170 | CGFloat brinkSize = TREE_WIDTH * 0.05;
171 | CGFloat branchSize = TREE_WIDTH * 0.2;
172 | UIBezierPath* pathBrink = [UIBezierPath new];
173 | UIBezierPath* pathBranch = [UIBezierPath new];
174 |
175 | CGFloat offset = percent * TREE_WIDTH * 0.3;
176 | NSMutableArray *array = [NSMutableArray array];
177 |
178 | CGFloat topBranch = TREE_HEIGHT * 0.6;
179 | [pathBranch moveToPoint:CGPointMake(offset, topBranch)];
180 | for (CGFloat y = topBranch; y > 0; y -= 0.1) {
181 | CGFloat p = y / topBranch;
182 | CGFloat xx = 1 - pow(1 - p, 0.5);
183 | CGFloat x = xx * offset;
184 | CGFloat xs = branchSize * (1 - p * p);
185 | [pathBranch addLineToPoint:CGPointMake(x - xs, y)];
186 | [array addObject:[NSValue valueWithCGPoint:CGPointMake(x + xs, y)]];
187 | }
188 | [pathBranch addLineToPoint:CGPointMake(-branchSize, 0)];
189 | [pathBranch addCurveToPoint:CGPointMake(branchSize, 0)
190 | controlPoint1:CGPointMake(-branchSize, -branchSize*1.5)
191 | controlPoint2:CGPointMake(branchSize, -branchSize*1.5)];
192 | for (int i = 0; i < array.count; i++) {
193 | [pathBranch addLineToPoint:[array[array.count-i-1] CGPointValue]];
194 | }
195 | [array removeAllObjects];
196 |
197 | [pathBranch applyTransform:CGAffineTransformMakeTranslation(offset * 0.3, 0)];
198 |
199 |
200 | CGFloat topBrink = TREE_HEIGHT * 0.7;
201 | [pathBrink moveToPoint:CGPointMake(offset, topBrink)];
202 | for (CGFloat y = topBrink; y > 0; y -= 0.1) {
203 | CGFloat p = (y / topBrink) * 0.7;
204 | CGFloat xx = 1 - pow(1 - p, 0.5);
205 | CGFloat x = xx * offset;
206 | CGFloat xs = brinkSize;
207 | if (p > 0.7 / 2) {
208 | xs = brinkSize * (0.7 - p) / 0.35;
209 | }
210 | [pathBrink addLineToPoint:CGPointMake(x - xs, y)];
211 | [array addObject:[NSValue valueWithCGPoint:CGPointMake(x + xs, y)]];
212 | }
213 | [pathBrink addLineToPoint:CGPointMake(-brinkSize, 0)];
214 | [pathBrink addLineToPoint:CGPointMake(brinkSize, 0)];
215 | for (int i = 0; i < array.count; i++) {
216 | [pathBrink addLineToPoint:[array[array.count-i-1] CGPointValue]];
217 | }
218 |
219 |
220 | [self setTreePathBrink:pathBrink];
221 | [self setTreePathBranch:pathBranch];
222 | }
223 |
224 | @end
225 |
--------------------------------------------------------------------------------
/SmartRefreshControl/Header/UIRefreshClassicsHeader.m:
--------------------------------------------------------------------------------
1 | //
2 | // UIRefreshClassicsHeader.m
3 | // Teecloud
4 | //
5 | // Created by Teeyun on 2020/8/17.
6 | // Copyright © 2020 SCWANG. All rights reserved.
7 | //
8 |
9 | #import "UIRefreshClassicsHeader.h"
10 |
11 | @interface UIRefreshClassicsHeader ()
12 |
13 | @property (nonatomic, strong) UILabel *labelTitle;
14 | @property (nonatomic, strong) UILabel *labelLastTime;
15 | @property (nonatomic, strong) UIImageView *viewArrow;
16 | @property (nonatomic, strong) UIActivityIndicatorView *viewLoading;
17 |
18 | @end
19 |
20 | @implementation UIRefreshClassicsHeader
21 |
22 | #pragma mark - Init
23 |
24 | /**
25 | * 初始化参数
26 | */
27 | - (void)setUpComponent {
28 | [super setUpComponent];
29 |
30 | [self setHeight:85];
31 | [self setSpaceOfTitleAndTime:5];
32 | [self setSpaceOfLoadingAndText:10];
33 | [self setScrollMode:UISmartScrollModeMove];
34 |
35 | [self addSubview:self.labelTitle];
36 | [self addSubview:self.labelLastTime];
37 | [self addSubview:self.viewArrow];
38 | [self addSubview:self.viewLoading];
39 | }
40 |
41 | - (void)layoutSubviews {
42 | [super layoutSubviews];
43 |
44 | CGSize sizeHeader = self.frame.size;
45 | CGFloat centerArrowX = sizeHeader.width / 2;
46 | CGFloat centerArrowY = sizeHeader.height / 2;
47 |
48 | if (!self.labelTitle.isHidden) {
49 | CGSize sizeTitle = [self.labelTitle sizeThatFits:self.bounds.size];
50 | CGRect rectTitle = CGRectMake(0, 0, sizeTitle.width, sizeTitle.height);
51 | CGFloat textWidth = sizeTitle.width;
52 |
53 | rectTitle.origin.x = (sizeHeader.width - sizeTitle.width) / 2;
54 |
55 | if (!self.labelLastTime.isHidden) {
56 | CGFloat space = self.spaceOfTitleAndTime;
57 | CGSize sizeTime = [self.labelLastTime sizeThatFits:self.bounds.size];
58 | CGRect rectTime = CGRectMake(0, 0, sizeTime.width, sizeTime.height);
59 |
60 | rectTime.origin.x = (sizeHeader.width - sizeTime.width) / 2;
61 | rectTime.origin.y = sizeHeader.height / 2 + space / 2;
62 | self.labelLastTime.frame = rectTime;
63 |
64 | rectTitle.origin.y = sizeHeader.height / 2 - space / 2 - sizeTitle.height;
65 |
66 | textWidth = MAX(sizeTitle.width, sizeTime.width);
67 | } else {
68 | rectTitle.origin.y = (sizeHeader.height - sizeTitle.height) / 2;
69 | }
70 |
71 | centerArrowX -= textWidth / 2 + self.spaceOfLoadingAndText;
72 |
73 | self.labelTitle.frame = rectTitle;
74 | }
75 |
76 | CGSize sizeLoading = self.viewLoading.frame.size;
77 | CGSize sizeArrow = self.viewArrow.image ? self.viewArrow.image.size : CGSizeZero;
78 | CGPoint originArrow = CGPointMake(centerArrowX - sizeArrow.width / 2, centerArrowY - sizeArrow.height / 2);
79 | self.viewArrow.frame = CGRectMake(originArrow.x, originArrow.y, sizeArrow.width, sizeArrow.height);
80 | self.viewArrow.center = CGPointMake(centerArrowX - sizeArrow.width / 2, centerArrowY);
81 | self.viewLoading.center = CGPointMake(centerArrowX - sizeLoading.width / 2, centerArrowY);
82 | }
83 |
84 | - (void)setColorAccent:(UIColor *)colorAccent {
85 | [super setColorAccent:colorAccent];
86 | self.labelTitle.textColor = colorAccent;
87 | self.labelLastTime.textColor = colorAccent;
88 | self.viewLoading.color = colorAccent;
89 | self.viewArrow.tintColor = colorAccent;
90 | }
91 |
92 | - (void)setColorPrimary:(UIColor *)colorPrimary {
93 | [super setColorPrimary:colorPrimary];
94 | self.backgroundColor = colorPrimary;
95 | }
96 |
97 | #pragma mark - Lazy
98 |
99 | - (UILabel *)labelTitle {
100 | if (_labelTitle == nil) {
101 | _labelTitle = [UILabel new];
102 | _labelTitle.font = [UIFont systemFontOfSize:15];
103 | }
104 | return _labelTitle;
105 | }
106 |
107 | - (UILabel *)labelLastTime {
108 | if (_labelLastTime == nil) {
109 | _labelLastTime = [UILabel new];
110 | _labelLastTime.font = [UIFont systemFontOfSize:12];
111 | }
112 | return _labelLastTime;
113 | }
114 |
115 | - (UIImageView *)viewArrow {
116 | if (_viewArrow == nil) {
117 | _viewArrow = [UIImageView new];
118 |
119 | CGFloat width = 18,height = 18;
120 | CGFloat lineWidth = width * 25 / 225;
121 | CGFloat vector1 = (lineWidth * 0.70710678118654752440084436210485f);//Math.sin(Math.PI/4));
122 | CGFloat vector2 = (lineWidth / 0.70710678118654752440084436210485f);//Math.sin(Math.PI/4));
123 |
124 | // NSMutableString *path = [NSMutableString string];
125 | // [path appendFormat:@"M%@,%@", @(width/2), @(height)];
126 | // [path appendFormat:@"L%@,%@", @(0), @(height / 2)];
127 | // [path appendFormat:@"L%@,%@", @(vector1), @(height / 2 - vector1)];
128 | // [path appendFormat:@"L%@,%@", @(width / 2 - lineWidth / 2), @(height - vector2 - lineWidth / 2)];
129 | // [path appendFormat:@"L%@,%@", @(width / 2 - lineWidth / 2), @(0)];
130 | // [path appendFormat:@"L%@,%@", @(width / 2 + lineWidth / 2), @(0)];
131 | // [path appendFormat:@"L%@,%@", @(width / 2 + lineWidth / 2), @(height - vector2 - lineWidth / 2)];
132 | // [path appendFormat:@"L%@,%@", @(width - vector1), @(height / 2 - vector1)];
133 | // [path appendFormat:@"L%@,%@", @(width), @(height / 2)];
134 | // [path appendString:@"Z"];
135 |
136 | // UIVectorView *view = [UIVectorView new];
137 | // [view parserPaths:@[path.copy]];
138 | // [view setFrame:CGRectMake(0, 0, width, height)];
139 |
140 | CGFloat scale = [UIScreen mainScreen].scale;
141 |
142 | UIGraphicsBeginImageContextWithOptions(CGSizeMake(width, height), NO, scale);
143 | CGContextRef context = UIGraphicsGetCurrentContext();
144 |
145 | CGContextMoveToPoint(context, width/2, height);
146 | CGContextAddLineToPoint(context, 0, height / 2);
147 | CGContextAddLineToPoint(context, vector1, height / 2 - vector1);
148 | CGContextAddLineToPoint(context, width / 2 - lineWidth / 2, height - vector2 - lineWidth / 2);
149 | CGContextAddLineToPoint(context, width / 2 - lineWidth / 2, 0);
150 | CGContextAddLineToPoint(context, width / 2 + lineWidth / 2, 0);
151 | CGContextAddLineToPoint(context, width / 2 + lineWidth / 2, height - vector2 - lineWidth / 2);
152 | CGContextAddLineToPoint(context, width - vector1, height / 2 - vector1);
153 | CGContextAddLineToPoint(context, width, height / 2);
154 | CGContextClosePath(context);
155 |
156 | CGContextFillPath(context);
157 | UIImage *img = UIGraphicsGetImageFromCurrentImageContext();
158 | UIGraphicsEndImageContext();
159 |
160 | _viewArrow.image = [img imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
161 | }
162 | return _viewArrow;
163 | }
164 |
165 | - (UIActivityIndicatorView *)viewLoading {
166 | if (_viewLoading == nil) {
167 | NSInteger style = 2;//UIActivityIndicatorViewStyleGray;
168 | _viewLoading = [UIActivityIndicatorView alloc];
169 | _viewLoading = [_viewLoading initWithActivityIndicatorStyle:style];
170 | _viewLoading.hidesWhenStopped = TRUE;
171 | }
172 | return _viewLoading;
173 | }
174 |
175 | #pragma mark - Override
176 |
177 | - (void)didMoveToWindow {
178 | [super didMoveToWindow];
179 | self.labelLastTime.text = self.textLastRefreshTime;
180 | }
181 |
182 | //- (void)scrollView:(UIScrollView *)scrollView didChange:(UIRefreshStatus)old status:(UIRefreshStatus)status {
183 | - (void)onStatus:(UIRefreshStatus)old changed:(UIRefreshStatus)status {
184 | // [super scrollView:scrollView didChange:old status:status];
185 | [super onStatus:old changed:status];
186 |
187 | switch (status) {
188 | case UIRefreshStatusWillRefresh:
189 | case UIRefreshStatusRefreshing:
190 | self.labelTitle.text = @"正在刷新...";
191 | [self setNeedsLayout];
192 | break;
193 | case UIRefreshStatusReleaseToRefresh:
194 | self.labelTitle.text = @"释放立即刷新";
195 | [self setNeedsLayout];
196 | break;
197 | default:
198 | self.labelTitle.text = @"下拉开始刷新";
199 | self.labelLastTime.text = self.textLastRefreshTime;
200 | [self setNeedsLayout];
201 | break;
202 | }
203 |
204 | if (status == UIRefreshStatusReleaseToRefresh) {
205 | [UIView animateWithDuration:self.durationNormal animations:^{
206 | self.viewArrow.transform = CGAffineTransformMakeRotation(M_PI);
207 | }];
208 | } else if (old == UIRefreshStatusReleaseToRefresh) {
209 | [UIView animateWithDuration:self.durationNormal animations:^{
210 | self.viewArrow.transform = CGAffineTransformIdentity;
211 | }];
212 | }
213 |
214 | if (status == UIRefreshStatusRefreshing) {
215 | self.viewArrow.hidden = true;
216 | [self.viewLoading startAnimating];
217 | } else if (old == UIRefreshStatusRefreshing) {
218 | self.viewArrow.hidden = false;
219 | [self.viewLoading stopAnimating];
220 | }
221 | }
222 |
223 | /*
224 | // Only override drawRect: if you perform custom drawing.
225 | // An empty implementation adversely affects performance during animation.
226 | - (void)drawRect:(CGRect)rect {
227 | // Drawing code
228 | }
229 | */
230 |
231 |
232 | @end
233 |
--------------------------------------------------------------------------------
/SmartRefreshControl/Header/UIRefreshFlyHeader.m:
--------------------------------------------------------------------------------
1 | //
2 | // UIRefreshFlyHeader.m
3 | // Refresh
4 | //
5 | // Created by SCWANG on 2021/6/13.
6 | // Copyright © 2021 Teeyun. All rights reserved.
7 | //
8 |
9 | #import "UIRefreshFlyHeader.h"
10 |
11 | #import "FlyView.h"
12 | #import "MountainView.h"
13 |
14 | #define FLOAT_VIEW_SIZE 50
15 | #define RGB(rgb)[UIColor colorWithRed:((float)((rgb&0xFF0000)>> 16))/ 255.0 green:((float)((rgb&0x00FF00)>> 8))/ 255.0 blue: ((float)(rgb&0x0000FF))/ 255.0 alpha:1]
16 |
17 | @interface UIRefreshHeader (UIRefreshFlyHeader)
18 |
19 | @property (nonatomic, weak) id originDelegate;
20 |
21 | - (void)adjustInset:(UIScrollView*) scrollView expand:(BOOL) expand;
22 | - (void)resetScrollContentOffset:(UIScrollView*) scrollView start:(CGFloat) start;
23 | - (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView;
24 |
25 | @end
26 |
27 | @interface UIRefreshFlyHeader ()
28 |
29 | @property (nonatomic, assign) BOOL attached;
30 | @property (nonatomic, strong) UIView *floatView;
31 | @property (nonatomic, strong) FlyView *flyView;
32 | @property (nonatomic, strong) MountainView *mountainView;
33 |
34 | @end
35 |
36 | @implementation UIRefreshFlyHeader
37 |
38 | - (void)setUpComponent {
39 | [self setHeight:150];
40 | [self setMountain:[MountainView new]];
41 | [self setFloatView:[UIView new]];
42 | [self setFlyView:[FlyView new]];
43 | [self setScrollMode:UISmartScrollModeStretch];
44 | [super setUpComponent];
45 |
46 | [self setTriggerRate:0.5];
47 | [self setColorPrimary:RGB(0x33aaff)];
48 | [self setColorAccent:UIColor.whiteColor];
49 | }
50 |
51 | - (void)layoutSubviews {
52 | [super layoutSubviews];
53 | [self.mountainView setFrame:self.bounds];
54 | [self.floatView setCenter:CGPointMake(FLOAT_VIEW_SIZE*3/4, self.frame.size.height)];
55 | if (!self.isRefreshing) {
56 | [self.flyView setCenter:CGPointMake(FLOAT_VIEW_SIZE*3/4, self.frame.size.height)];
57 | }
58 | }
59 |
60 | - (void)setFlyView:(FlyView *)flyView {
61 | _flyView = flyView;
62 | [self addSubview:flyView];
63 | }
64 |
65 | - (void)setFloatView:(UIView *)floatView {
66 | _floatView = floatView;
67 | [floatView setFrame:CGRectMake(0, 0, FLOAT_VIEW_SIZE, FLOAT_VIEW_SIZE)];
68 | [floatView.layer setCornerRadius:FLOAT_VIEW_SIZE/2];
69 | [self addSubview:floatView];
70 | }
71 |
72 | - (void)setMountain:(MountainView *)mountainView {
73 | _mountainView = mountainView;
74 | [self addSubview:mountainView];
75 | }
76 |
77 | /**
78 | * 颜色绑定
79 | */
80 | - (void)setColorPrimary:(UIColor *)colorPrimary {
81 | [super setColorPrimary:colorPrimary];
82 | [self.floatView setBackgroundColor:colorPrimary];
83 | [self.mountainView setColorPrimary:colorPrimary];
84 | }
85 |
86 | /**
87 | * 颜色绑定
88 | */
89 | - (void)setColorAccent:(UIColor *)colorAccent {
90 | [super setColorAccent:colorAccent];
91 | [self.flyView setColorAccent:colorAccent];
92 | }
93 |
94 | /**
95 | * 打开常态展开
96 | */
97 | - (void)scrollView:(UIScrollView *)scrollView attached:(BOOL)attach {
98 | [super scrollView:scrollView attached:attach];
99 | [self adjustInset:scrollView expand:TRUE];
100 | [self setAttached:TRUE];
101 | }
102 |
103 | /**
104 | * 关闭常态展开
105 | */
106 | - (void)scrollView:(UIScrollView *)scrollView detached:(BOOL)detach {
107 | [self setAttached:FALSE];
108 | [super scrollView:scrollView detached:detach];
109 | [self adjustInset:scrollView expand:FALSE];
110 | }
111 |
112 | /**
113 | * 重写 dragOffset
114 | * 父类通过比较 dragOffset 和 lastDragOffset 不一致
115 | * 来触发 onScrollingWithOffset,和布局操作
116 | * 但是 因为本 Header 默认展开了,offset 也被重写为 0
117 | * 导致 dragOffset 和 lastDragOffset 都为0,
118 | * 无法布局 setFrame 导致 height = 0
119 | * 所以在 height = 0 的时候强行返回 -1,可以触发 布局操作
120 | */
121 | - (CGFloat)dragOffset {
122 | if (self.height > 0) {
123 | return super.dragOffset;
124 | } else {
125 | return -1;
126 | }
127 | }
128 |
129 | /**
130 | * 下拉时候,根据偏移量改变 背景高度,和纸飞机角度
131 | */
132 | - (void)onScrollingWithOffset:(CGFloat)offset percent:(CGFloat)percent drag:(BOOL)isDragging {
133 | [self.mountainView onScrollingWithOffset:offset percent:percent];
134 | if (!self.isRefreshing) {
135 | percent = offset / self.expandHeight;
136 | [self.flyView setTransform:CGAffineTransformMakeRotation(-percent*3.1415926/4)];
137 | }
138 | }
139 |
140 | /**
141 | * 本刷新常态展开,但是展开关闭的逻辑封装在父类,通过调用 adjustInset 实现
142 | * 重写 adjustInset 实现根据情况忽略父类的展开关闭操作
143 | */
144 | - (void)adjustInset:(UIScrollView *)scrollView expand:(BOOL)expand {
145 | if (!self.attached) {
146 | [super adjustInset:scrollView expand:expand];
147 | } else {
148 | if (self.dragOffset != 0) {
149 | self.originDelegate = scrollView.delegate;
150 | scrollView.delegate = self;
151 | } else {
152 | [super scrollViewDidEndScrollingAnimation:scrollView];
153 | }
154 | }
155 | }
156 |
157 | /**
158 | * 重写 resetScrollContentOffset 并不任何操作
159 | * 因为 第一次展开之后,再也没有展开和关闭操作,
160 | * offset 重置UITableView自己会完成
161 | */
162 | - (void)resetScrollContentOffset:(UIScrollView *)scrollView start:(CGFloat)start {
163 | }
164 |
165 | /**
166 | * 本刷新常态展开,之后没有再展开和关闭的操作,
167 | * 没有 setContentOffset 操作,就不会触发 scrollViewDidEndScrollingAnimation
168 | * 松手时需要 scrollViewDidEndDecelerating 转发 scrollViewDidEndScrollingAnimation
169 | */
170 | - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
171 | [super scrollViewDidEndScrollingAnimation: scrollView];
172 | }
173 |
174 | /**
175 | * 重写获取 insetTop
176 | * 因为本刷新展开为常态,所以 offset 和 percent 都不为 0
177 | * 重写 insetTop 可以让 offset percent 归零
178 | */
179 | - (CGFloat)finalyContentInsetsFrom:(UIScrollView*) scrollView {
180 | return [super finalyContentInsetsFrom:scrollView] + self.expandHeight;
181 | }
182 |
183 | /**
184 | * 重写Frame计算
185 | * 因为 finalyContentInsetsFrom 的重写让实际展开的 Header 在计算时变成未展开的状态
186 | * 但是实际布局又需要布局成展开的状态,所以 insetTop-expandHeight
187 | */
188 | - (void)adjustFrameWithHeight:(CGFloat)expandHeight inset:(CGFloat)inset expand:(BOOL)isExpanded offset:(CGFloat)offset {
189 | [super adjustFrameWithHeight:expandHeight inset:inset-expandHeight expand:isExpanded offset:offset];
190 | }
191 |
192 | /**
193 | * 用户松手,执行飞机飞出动画
194 | */
195 | - (void)onStartAnimationWhenRealeasing {
196 |
197 | self.flyView.transform = CGAffineTransformIdentity;
198 |
199 | CGPoint end = CGPointMake(self.width + FLOAT_VIEW_SIZE / 2, FLOAT_VIEW_SIZE / 4);
200 | UIBezierPath* path = UIBezierPath.bezierPath;
201 | [path moveToPoint: CGPointMake(FLOAT_VIEW_SIZE*3/4, self.expandHeight + self.dragOffset)];
202 | [path addQuadCurveToPoint:end controlPoint:CGPointMake(0, 0)];
203 |
204 | CAKeyframeAnimation *aniFly = [CAKeyframeAnimation animationWithKeyPath:@"position"];
205 | aniFly.path = path.CGPath;
206 | aniFly.calculationMode = kCAAnimationPaced;
207 | aniFly.rotationMode = kCAAnimationRotateAuto;
208 |
209 | CABasicAnimation *aniScale = [CABasicAnimation animationWithKeyPath:@"transform"];
210 | aniScale.fromValue = [NSValue valueWithCATransform3D:CATransform3DIdentity];
211 | aniScale.toValue = [NSValue valueWithCATransform3D:CATransform3DMakeScale(0.5, 0.2, 1)];
212 |
213 | CAAnimationGroup *aniGroup = [[CAAnimationGroup alloc] init];
214 | aniGroup.animations = @[aniScale, aniFly];
215 | aniGroup.duration = 1.5f;
216 | aniGroup.fillMode = kCAFillModeForwards;
217 | aniGroup.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
218 |
219 | [self.flyView.layer addAnimation:aniGroup forKey:@"fly"];
220 | [self.flyView setCenter:end];
221 | }
222 |
223 | /**
224 | * 用户关闭刷新,执行飞机飞回动画
225 | */
226 | - (void)finishRefreshWithSuccess:(BOOL)success {
227 | [super finishRefreshWithSuccess:success];
228 |
229 | CGSize size = self.frame.size;
230 |
231 | CGPoint p1 = CGPointMake(-FLOAT_VIEW_SIZE, size.height - FLOAT_VIEW_SIZE);
232 | CGPoint p2 = CGPointMake(-FLOAT_VIEW_SIZE, size.height);
233 | CGPoint end = CGPointMake(FLOAT_VIEW_SIZE*3/4, self.frame.size.height);
234 | UIBezierPath* path = UIBezierPath.bezierPath;
235 | [path moveToPoint: self.flyView.center];
236 | [path addQuadCurveToPoint:p1 controlPoint:CGPointMake(size.width, size.height - FLOAT_VIEW_SIZE)];
237 | [path addQuadCurveToPoint:end controlPoint: p2];
238 |
239 | CAKeyframeAnimation *aniFly = [CAKeyframeAnimation animationWithKeyPath:@"position"];
240 | aniFly.path = path.CGPath;
241 | aniFly.calculationMode = kCAAnimationPaced;
242 | aniFly.rotationMode = kCAAnimationRotateAuto;
243 |
244 | CABasicAnimation *aniScale = [CABasicAnimation animationWithKeyPath:@"transform"];
245 | aniScale.fromValue = [NSValue valueWithCATransform3D:CATransform3DMakeScale(0.5, 0.2, 1)];
246 | aniScale.toValue = [NSValue valueWithCATransform3D:CATransform3DIdentity];
247 |
248 | CAAnimationGroup *aniGroup = [[CAAnimationGroup alloc] init];
249 | aniGroup.animations = @[aniScale, aniFly];
250 | aniGroup.duration = 1.5f;
251 | aniGroup.delegate = self;
252 | aniGroup.fillMode = kCAFillModeForwards;
253 | aniGroup.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
254 |
255 | [self.flyView.layer addAnimation:aniGroup forKey:@"back"];
256 | }
257 |
258 | /**
259 | * 关闭刷新,纸飞机飞回动画结束,需要通知父类处理
260 | */
261 | - (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag {
262 | if (flag) {
263 | [self.flyView setCenter:CGPointMake(FLOAT_VIEW_SIZE*3/4, self.expandHeight)];
264 | [super scrollViewDidEndScrollingAnimation: self.scrollView];
265 | }
266 | }
267 |
268 |
269 | //// Only override drawRect: if you perform custom drawing.
270 | //// An empty implementation adversely affects performance during animation.
271 | //- (void)drawRect:(CGRect)rect {
272 | // // Drawing code
273 | // if (!self.attached) {
274 | // [self adjustInset:self.scrollView expand:TRUE];
275 | // [self setAttached:TRUE];
276 | // }
277 | //}
278 |
279 |
280 | @end
281 |
--------------------------------------------------------------------------------
/SmartRefreshDemo/Demo/UIDemoHeaderController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DemoTableController.swift
3 | // Refresh
4 | //
5 | // Created by SCWANG on 2021/6/20.
6 | // Copyright © 2021 Teeyun. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class UIDemoHeaderController: UITableViewController {
12 |
13 | class Item {
14 | let name: String
15 | let detail: String?
16 | let block: (() -> Void)?
17 | init(_ name: String, _ detail: String? = nil, _ block: (() -> Void)? = nil) {
18 | self.name = name;
19 | self.block = block;
20 | self.detail = detail;
21 | }
22 | }
23 |
24 | class Group {
25 | let title: String
26 | let items: [Item]
27 | init(_ title: String, _ items: [Item]) {
28 | self.title = title;
29 | self.items = items;
30 | }
31 | }
32 |
33 | override init(style: UITableView.Style) {
34 | super.init(style: .grouped)
35 | }
36 |
37 | required init?(coder: NSCoder) {
38 | super.init(coder: coder)
39 | }
40 |
41 | var colorAccent: UIColor = UIColor.white
42 | var colorPrimary: UIColor = UIColor.clear
43 | var navTranslucent: Bool = true;
44 | var navTintColor: UIColor = UIColor.black;
45 | var navBarTintColor: UIColor? = UIColor.white;
46 | var navBackgroundColor: UIColor? = UIColor.white;
47 | var navTitleTextAttributes: [NSAttributedString.Key : Any]? = nil
48 |
49 | lazy var groups: [Group] = {
50 | return self.initGroupItems();
51 | }()
52 |
53 | deinit {
54 | NSLog("deinit-%@", self)
55 | }
56 |
57 | lazy var header: UIRefreshHeader = {
58 | let header = self.initRefreshHeader();
59 | self.colorAccent = header.colorAccent;
60 | self.colorPrimary = header.colorPrimary;
61 | NSLog("initRefreshHeader-%@", header);
62 | return header;
63 | }()
64 |
65 | func initRefreshHeader() -> UIRefreshHeader {
66 | return UIRefreshClassicsHeader.init();
67 | }
68 |
69 | func initGroupItems() -> [Group]{
70 | NSLog("initGroupItems");
71 | return [
72 | Group(" ", [
73 | Item("默认主题", "点击还原") {[weak self] () in
74 | self?.changeTheme(self!.colorPrimary, self!.colorAccent)
75 | },
76 | Item("橙色主题", "点击切换") {[weak self] () in
77 | self?.changeTheme(UIColor.systemOrange, UIColor.white)
78 | },
79 | Item("红色主题", "点击切换") {[weak self] () in
80 | self?.changeTheme(UIColor.systemRed, UIColor.white)
81 | },
82 | Item("绿色主题", "点击切换") {[weak self] () in
83 | self?.changeTheme(UIColor.systemGreen, UIColor.white)
84 | },
85 | Item("蓝色主题", "点击切换") {[weak self] () in
86 | self?.changeTheme(UIColor.systemBlue, UIColor.white)
87 | },
88 | ]),
89 | Group("测试数据", [
90 | Item("测试数据1"),
91 | Item("测试数据2"),
92 | Item("测试数据3"),
93 | Item("测试数据4"),
94 | Item("测试数据5"),
95 | Item("测试数据6"),
96 | Item("测试数据7"),
97 | Item("测试数据8"),
98 | ])
99 | ]
100 | }
101 |
102 | func changeTheme(_ primary: UIColor, _ accent: UIColor) {
103 | self.header.colorAccent = accent;
104 | self.header.colorPrimary = primary;
105 | self.header.beginRefresh();
106 | guard let nav = self.navigationController?.navigationBar else {
107 | return
108 | }
109 | if primary == self.colorPrimary && accent == self.colorAccent {
110 | nav.isTranslucent = self.navTranslucent
111 | nav.tintColor = self.navTintColor
112 | nav.barTintColor = self.navBarTintColor
113 | nav.backgroundColor = self.navBackgroundColor
114 | nav.titleTextAttributes = self.navTitleTextAttributes
115 | } else {
116 | nav.isTranslucent = false;
117 | nav.tintColor = accent;
118 | nav.barTintColor = primary;
119 | nav.backgroundColor = primary;
120 | nav.titleTextAttributes = [NSAttributedString.Key.foregroundColor:accent];
121 | }
122 | self.setNeedsStatusBarAppearanceUpdate()
123 | }
124 |
125 | override var preferredStatusBarStyle: UIStatusBarStyle {
126 | return self.navigationController?.navigationBar.isTranslucent ?? false ? .default : .lightContent;
127 | }
128 |
129 | // override var childForStatusBarStyle: UIViewController? {
130 | // return self.top
131 | // }
132 |
133 | override func viewDidLoad() {
134 | super.viewDidLoad()
135 | self.title = (type(of: self)).description()
136 | .replacingOccurrences(of: "UI", with: "")
137 | .replacingOccurrences(of: "SmartRefreshDemo.", with: "")
138 | .replacingOccurrences(of: "Controller", with: "")
139 |
140 | self.header.attach(self.tableView);
141 | self.header.addTarget(self, action: #selector(onRefresh), for: .valueChanged)
142 | self.header.beginRefresh()
143 | // Uncomment the following line to preserve selection between presentations
144 | // self.clearsSelectionOnViewWillAppear = false
145 |
146 | // Uncomment the following line to display an Edit button in the navigation bar for this view controller.
147 | // self.navigationItem.rightBarButtonItem = self.editButtonItem
148 | }
149 |
150 | override func viewWillAppear(_ animated: Bool) {
151 | super.viewWillAppear(animated);
152 | guard let nav = self.navigationController?.navigationBar else {
153 | return
154 | }
155 | self.navTranslucent = nav.isTranslucent;
156 | self.navTintColor = nav.tintColor;
157 | self.navBarTintColor = nav.barTintColor;
158 | self.navBackgroundColor = nav.backgroundColor;
159 | self.navTitleTextAttributes = nav.titleTextAttributes;
160 | }
161 |
162 | override func viewWillDisappear(_ animated: Bool) {
163 | super.viewWillDisappear(animated);
164 | guard let nav = self.navigationController?.navigationBar else {
165 | return
166 | }
167 | nav.isTranslucent = self.navTranslucent
168 | nav.tintColor = self.navTintColor
169 | nav.barTintColor = self.navBarTintColor
170 | nav.backgroundColor = self.navBackgroundColor
171 | nav.titleTextAttributes = self.navTitleTextAttributes
172 | }
173 |
174 | @objc func onRefresh() {
175 | DispatchQueue.main.asyncAfter(deadline: .now() + 2.5) {
176 | self.header.finishRefresh();
177 | }
178 | }
179 |
180 | // MARK: - Table view data source
181 |
182 | override func numberOfSections(in tableView: UITableView) -> Int {
183 | // #warning Incomplete implementation, return the number of sections
184 | return groups.count
185 | }
186 |
187 | override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
188 | // #warning Incomplete implementation, return the number of rows
189 | return groups[section].items.count;
190 | }
191 |
192 | override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
193 | return section == 0 ? 0.001 : 25
194 | // return super.tableView(tableView, heightForHeaderInSection: section);
195 | }
196 |
197 | override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
198 | return groups[section].title;
199 | }
200 |
201 | override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
202 | // let cell = tableView.dequeueReusableCell(withIdentifier: "item", for: indexPath)
203 |
204 | let cell = tableView.dequeueReusableCell(withIdentifier: "item")
205 | ?? UITableViewCell.init(style: .value1, reuseIdentifier: "item")
206 |
207 | // Configure the cell...
208 | cell.textLabel?.text = self.groups[indexPath.section].items[indexPath.row].name;
209 |
210 | cell.detailTextLabel?.text = self.groups[indexPath.section].items[indexPath.row].detail;
211 |
212 | return cell
213 | }
214 |
215 | override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
216 | tableView.deselectRow(at: indexPath, animated: true);
217 | self.groups[indexPath.section].items[indexPath.row].block?()
218 | }
219 | /*
220 | // Override to support conditional editing of the table view.
221 | override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
222 | // Return false if you do not want the specified item to be editable.
223 | return true
224 | }
225 | */
226 |
227 | /*
228 | // Override to support editing the table view.
229 | override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
230 | if editingStyle == .delete {
231 | // Delete the row from the data source
232 | tableView.deleteRows(at: [indexPath], with: .fade)
233 | } else if editingStyle == .insert {
234 | // Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view
235 | }
236 | }
237 | */
238 |
239 | /*
240 | // Override to support rearranging the table view.
241 | override func tableView(_ tableView: UITableView, moveRowAt fromIndexPath: IndexPath, to: IndexPath) {
242 |
243 | }
244 | */
245 |
246 | /*
247 | // Override to support conditional rearranging of the table view.
248 | override func tableView(_ tableView: UITableView, canMoveRowAt indexPath: IndexPath) -> Bool {
249 | // Return false if you do not want the item to be re-orderable.
250 | return true
251 | }
252 | */
253 |
254 | /*
255 | // MARK: - Navigation
256 |
257 | // In a storyboard-based application, you will often want to do a little preparation before navigation
258 | override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
259 | // Get the new view controller using segue.destination.
260 | // Pass the selected object to the new view controller.
261 | }
262 | */
263 |
264 | }
265 |
--------------------------------------------------------------------------------
/SmartRefreshControl/Header/UIRefreshStoreHouseHeader.m:
--------------------------------------------------------------------------------
1 | //
2 | // UIRefreshStoreHouseHeader.m
3 | // Refresh
4 | //
5 | // Created by Teeyun on 2021/2/1.
6 | // Copyright © 2021 Teeyun. All rights reserved.
7 | //
8 |
9 | #import "UIRefreshStoreHouseHeader.h"
10 |
11 | #import "ValueAnimator.h"
12 | #import "StoreHousePath.h"
13 |
14 | #define toAlpha 0.4f
15 | #define fromAlpha 1.0f
16 | #define darkAlpha 0.4f
17 | #define internalAnimationFactor 0.7f
18 | #define loadingAniItemDuration 0.4f
19 | #define DEFAULT_SCALE 1.0f
20 | #define DEFAULT_PADDING 1.5f
21 |
22 | @interface UIRefreshStoreHouseHeader ()
23 |
24 | @property (nonatomic, assign) CGFloat scale;
25 | //@property (nonatomic, assign) CGFloat dropHeight;
26 | @property (nonatomic, assign) CGFloat horzontalRandomness;
27 |
28 | //@property (nonatomic, assign) CGFloat progress;
29 | @property (nonatomic, assign) CGFloat padding;
30 | @property (nonatomic, assign) CGRect linesRect;
31 |
32 | @property (nonatomic, strong) UIColor *colorText;
33 |
34 | //@property (nonatomic, assign) BOOL isInLoading;
35 |
36 | @property (nonatomic, strong) NSTimer *timerLoading;
37 | @property (nonatomic, strong) ValueAnimator *animatorLoading;
38 | @property (nonatomic, strong) ValueAnimator *animatorFinishing;
39 |
40 | @property (nonatomic, copy) NSArray *lines;
41 | @end
42 |
43 | @implementation UIRefreshStoreHouseHeader
44 |
45 | + (instancetype)newWithString:(NSString *)string {
46 | return [self newWithString:string scale:DEFAULT_SCALE];
47 | }
48 |
49 | + (instancetype)newWithString:(NSString *)string scale:(CGFloat)scale {
50 | UIRefreshStoreHouseHeader* header = [self new];
51 | if (header) {
52 | [header initLinesWithString:string scale:scale];
53 | }
54 | return header;
55 | }
56 |
57 | + (instancetype)newWithString:(NSString *)string scale:(CGFloat)scale padding:(CGFloat)padding {
58 | UIRefreshStoreHouseHeader* header = [self new];
59 | if (header) {
60 | [header initLinesWithString:string scale:scale padding:padding];
61 | }
62 | return header;
63 | }
64 |
65 | - (instancetype)init
66 | {
67 | self = [super init];
68 | if (self) {
69 | [self initLinesWithString:@"store house"];
70 | }
71 | return self;
72 | }
73 |
74 | - (void)initLinesWithString:(NSString *)string {
75 | [self initLinesWithString:string scale:DEFAULT_SCALE];
76 | }
77 |
78 | - (void)initLinesWithString:(NSString *)string scale:(CGFloat)scale {
79 | [self initLinesWithString:string scale:scale padding:DEFAULT_PADDING];
80 | }
81 |
82 | - (void)initLinesWithString:(NSString *)string scale:(CGFloat)scale padding:(CGFloat)padding {
83 | [self setLines:[StoreHousePath linesFrom:string scale:scale space:10]];
84 | [self setPadding:padding];
85 | [self updateLinesRect];
86 | }
87 |
88 | - (void)initLinesWithArray:(NSArray *)array {
89 | [self initLinesWithArray:array padding:0.5];
90 | }
91 |
92 | - (void)initLinesWithArray:(NSArray *)array padding:(CGFloat)padding {
93 | [self setLines:[StoreHousePath linesFromArray:array]];
94 | [self setPadding:padding];
95 | [self updateLinesRect];
96 | }
97 |
98 | - (void)updateLinesRect {
99 | CGRect linesRect = CGRectZero;
100 | for (StoreHouseLine* line in self.lines) {
101 | linesRect.size.width = MAX(linesRect.size.width, line.end.x);
102 | linesRect.size.width = MAX(linesRect.size.width, line.start.x);
103 | linesRect.size.height = MAX(linesRect.size.height, line.end.y);
104 | linesRect.size.height = MAX(linesRect.size.height, line.start.y);
105 | }
106 | CGFloat padding = self.padding > 5 ? self.padding : self.padding * linesRect.size.height;
107 |
108 | linesRect.origin.y = padding;
109 | linesRect.origin.x = self.width / 2 - linesRect.size.width / 2;
110 |
111 | self.linesRect = linesRect;
112 | self.height = padding * 2 + linesRect.size.height;
113 | // self.dropHeight = self.height;
114 | }
115 |
116 | /**
117 | * 初始化参数
118 | */
119 | - (void)setUpComponent {
120 | [super setUpComponent];
121 |
122 | self.opaque = FALSE;
123 |
124 | self.scrollMode = UISmartScrollModeStretch;
125 | self.colorText = [UIColor whiteColor];
126 | self.colorAccent = [UIColor whiteColor];
127 | self.colorPrimary = [UIColor colorWithRed:0.3 green:0.3 blue:0.3 alpha:1.0];
128 |
129 | // self.loadingAniDuration = 1.f;
130 | // self.loadingAniSegDuration = 1.f;
131 |
132 | self.lineWidth = 2;
133 |
134 | self.enableFadeAnimation = TRUE;
135 | self.enableReverseFlash = FALSE;
136 |
137 | self.padding = DEFAULT_PADDING;
138 | }
139 |
140 | - (void)setEnableFadeAnimation:(BOOL)enableFadeAnimation {
141 | _enableFadeAnimation = enableFadeAnimation;
142 | self.finishDuration = enableFadeAnimation * 0.5;
143 | }
144 |
145 | - (void)onScrollingWithOffset:(CGFloat)offset percent:(CGFloat)percent drag:(BOOL)isDragging {
146 | [self setNeedsDisplay];
147 | if (self.status != UIRefreshStatusFinish) {
148 | [self updateLinesWithProgress:percent];
149 | }
150 | }
151 |
152 | - (void)onStartAnimationWhenRealeasing {
153 | [super onStartAnimationWhenRealeasing];
154 |
155 | [self setAnimatorLoading:[ValueAnimator newWithTarget:self selector:@selector(onAnimationLoading:)]];
156 | [self.animatorLoading setRepeatCount:INFINITY];
157 | [self.animatorLoading start];
158 |
159 | SEL action = @selector(onAnimationTimer);
160 | NSTimer *timer = [NSTimer timerWithTimeInterval:0.075 target:self selector:action userInfo:nil repeats:TRUE];
161 | [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
162 | [self setTimerLoading:timer];
163 | }
164 |
165 | - (CGFloat)onRefreshFinishing:(BOOL)success {
166 | if (!success) {
167 | return 0;
168 | }
169 | [self setAnimatorFinishing:[ValueAnimator newWithTarget:self selector:@selector(onAnimationFinishing:)]];
170 | [self.animatorFinishing setFrom:1 to:0];
171 | [self.animatorFinishing setDuration:self.finishDuration*3];
172 | [self.animatorFinishing start];
173 | return self.finishDuration;
174 | }
175 |
176 | - (void)onAnimationTimer {
177 | static NSUInteger index = 0;
178 | if (self.isRefreshing) {
179 | if (index < self.lines.count) {
180 | self.lines[_enableReverseFlash?(self.lines.count - ++index):index++].alpha = 1;
181 | } else if (++index - self.lines.count == 2){
182 | index = 0;
183 | }
184 | } else {
185 | index = 0;
186 | [self.timerLoading invalidate];
187 | [self setTimerLoading:nil];
188 | }
189 | }
190 |
191 | - (void)onAnimationLoading:(ValueAnimator*) animator {
192 | if (self.isRefreshing) {
193 | for (StoreHouseLine* line in self.lines) {
194 | if (line.alpha > darkAlpha) {
195 | line.alpha -= 0.01;
196 | }
197 | }
198 | [self setNeedsDisplay];
199 | } else {
200 | [animator stop];
201 | }
202 | }
203 |
204 | - (void)onAnimationFinishing:(ValueAnimator*) animator {
205 | if (self.status == UIRefreshStatusFinish) {
206 | // NSLog(@"onAnimationFinishing = %@", @(animator.value));
207 | [self updateLinesWithProgress:animator.value];
208 | [self setNeedsDisplay];
209 | } else {
210 | [animator stop];
211 | }
212 | }
213 |
214 | - (void)setFrame:(CGRect)frame {
215 | if (_lines.count && frame.size.width != self.frame.size.width) {
216 | _linesRect.origin.x = frame.size.width / 2 - _linesRect.size.width / 2;
217 | }
218 | [super setFrame:frame];
219 | }
220 |
221 | // Only override drawRect: if you perform custom drawing.
222 | // An empty implementation adversely affects performance during animation.
223 | - (void)drawRect:(CGRect)rect {
224 | // Drawing code
225 | const CGFloat width = self.width;
226 | const CGFloat height = self.dragOffset;
227 | const CGContextRef context = UIGraphicsGetCurrentContext();
228 |
229 | [self.colorPrimary setFill];
230 | CGContextFillRect(context, CGRectMake(0, 0, width, height));
231 |
232 | CGContextSetLineWidth(context, self.lineWidth);
233 | CGContextTranslateCTM(context, self.linesRect.origin.x , self.linesRect.origin.y);
234 |
235 | for (StoreHouseLine* line in self.lines) {
236 | [[self.colorAccent colorWithAlphaComponent:line.alpha] setStroke];
237 | CGContextSaveGState(context);
238 | CGContextConcatCTM(context, line.transform);
239 | CGContextMoveToPoint(context, line.start.x, line.start.y);
240 | CGContextAddLineToPoint(context, line.end.x, line.end.y);
241 | CGContextStrokePath(context);
242 | CGContextRestoreGState(context);
243 | }
244 |
245 | if (height <= 0) {
246 | NSLog(@"%@.drawRect.height=%@ !", self.class, @(height));
247 | }
248 | }
249 |
250 | - (void)updateLinesWithProgress:(CGFloat)progress
251 | {
252 | for (NSUInteger i = 0, len = self.lines.count; i < len; i++) {
253 | CGFloat paddingStart = (1 - internalAnimationFactor) / len * i;
254 | CGFloat paddingEnd = 1 - internalAnimationFactor - paddingStart;
255 |
256 | StoreHouseLine* line = self.lines[i];
257 | if (progress == 1 || progress >= 1 - paddingEnd) {
258 | line.alpha = self.isRefreshing ? line.alpha:darkAlpha;
259 | line.transform = CGAffineTransformIdentity;
260 | } else if (progress == 0) {
261 | self.transform = CGAffineTransformMakeTranslation(line.offset*self.width, -self.expandHeight);
262 | } else if (!self.isRefreshing) {
263 | CGFloat realProgress;
264 | if (progress <= paddingStart)
265 | realProgress = 0;
266 | else
267 | realProgress = MIN(1, (progress - paddingStart)/internalAnimationFactor);
268 | line.transform = CGAffineTransformMakeTranslation(line.offset*self.width*(1-realProgress), -self.expandHeight*(1-realProgress));
269 | line.transform = CGAffineTransformTranslate(line.transform, line.middle.x, line.middle.y);
270 | line.transform = CGAffineTransformRotate(line.transform, M_PI*(realProgress));
271 | line.transform = CGAffineTransformScale(line.transform, realProgress, realProgress);
272 | line.transform = CGAffineTransformTranslate(line.transform, -line.middle.x, -line.middle.y);
273 | line.alpha = realProgress * darkAlpha;
274 | }
275 | }
276 | }
277 | @end
278 |
--------------------------------------------------------------------------------
/SmartRefreshControl/Header/UIRefreshGameBattleCityHeader.m:
--------------------------------------------------------------------------------
1 | //
2 | // UIRefreshGameBattleCityHeader.m
3 | // Refresh
4 | //
5 | // Created by SCWANG on 2021/6/9.
6 | // Copyright © 2021 Teeyun. All rights reserved.
7 | //
8 |
9 | #import "UIRefreshGameBattleCityHeader.h"
10 |
11 | #import "UIRefreshGamePlayingScene.h"
12 |
13 | #define NAME_PLAYER @"player"
14 | #define NAME_TANK @"tank"
15 | #define NAME_BULLET @"bullet"
16 | #define NAME_WORLD @"world"
17 |
18 | #define MASK_PLAYER 0x1
19 | #define MASK_TANK 0x2
20 | #define MASK_BULLET 0x4
21 | #define MASK_WORLD 0x8
22 |
23 | #define TANK_ROW_NUM 3 //轨道数量
24 | #define TANK_BARREL_RATIO (1.0/3) //炮管尺寸所在tank尺寸的比率
25 | #define BULLET_NUM_SPACING 200 //默认子弹之间空隙间距
26 | #define TANK_NUM_SPACING 40 //默认敌方坦克之间间距
27 |
28 | @interface BattleCityScene : UIRefreshGamePlayingScene
29 |
30 | @property (nonatomic, strong) SKShapeNode *nodePlayer;
31 | @property (nonatomic, strong) UIBezierPath *pathPlayer;
32 | @property (nonatomic, strong) SKShapeNode *nodeBullet;
33 | @property (nonatomic, strong) UIBezierPath *pathBullet;
34 | @property (nonatomic, strong) SKShapeNode *nodeEnemy;
35 | @property (nonatomic, strong) UIBezierPath *pathEnemy;
36 |
37 | @property (nonatomic, assign) CGFloat spaceEnemy;
38 | @property (nonatomic, assign) CGFloat spaceBullet;
39 |
40 | @property (nonatomic, assign) BOOL isGameRunning;
41 |
42 | @end
43 |
44 | @interface UIRefreshGameHeader (UIRefreshGameBattleCityHeader)
45 |
46 | - (UIRefreshGamePlayingScene*) newPlayingScene;
47 |
48 | @end
49 |
50 | @implementation UIRefreshGameBattleCityHeader
51 |
52 | - (UIRefreshGamePlayingScene *)newPlayingScene {
53 | return [BattleCityScene new];
54 | }
55 |
56 | /*
57 | // Only override drawRect: if you perform custom drawing.
58 | // An empty implementation adversely affects performance during animation.
59 | - (void)drawRect:(CGRect)rect {
60 | // Drawing code
61 | }
62 | */
63 |
64 | @end
65 |
66 | @implementation BattleCityScene
67 |
68 | - (instancetype)init
69 | {
70 | self = [super init];
71 | if (self) {
72 | [self initize];
73 | }
74 | return self;
75 | }
76 |
77 | - (void)initize {
78 | [self setName:@"BattleCityScene"];
79 | [self setScaleMode:SKSceneScaleModeFill];
80 | [self.physicsWorld setGravity:CGVectorMake(0, 0)];
81 | [self.physicsWorld setContactDelegate:self];
82 |
83 | [self setPathPlayer:[UIBezierPath bezierPathWithRect:CGRectMake(0, 0, 20, 20)]];
84 |
85 | [self setNodePlayer:[SKShapeNode new]];
86 | [self.nodePlayer setName:NAME_PLAYER];
87 | [self addChild: self.nodePlayer];
88 | }
89 |
90 | - (void)setColorAccent:(UIColor *)colorAccent {
91 | [super setColorAccent:colorAccent];
92 | [self.nodePlayer setFillColor:colorAccent];
93 | }
94 |
95 | - (void)didChangeSize:(CGSize)oldSize {
96 | [super didChangeSize:oldSize];
97 |
98 | CGSize size = self.size;
99 |
100 | id body = [SKPhysicsBody bodyWithEdgeLoopFromRect:self.frame];
101 | [self setPhysicsBody:body];
102 | [self.physicsBody setCategoryBitMask:MASK_WORLD];
103 | [self.physicsBody setFriction:0.0];
104 | [self.physicsBody setRestitution:0.1];
105 | [self.physicsBody setDynamic:YES];
106 |
107 | CGFloat sizeTank = size.height / TANK_ROW_NUM;
108 | CGFloat halfTank = sizeTank / 2;
109 | CGFloat sizeBarrel = sizeTank * TANK_BARREL_RATIO;
110 | CGFloat paddingBarrel = (sizeTank - sizeBarrel)/2;
111 |
112 | [self setPathPlayer:[UIBezierPath new]];
113 | [self.pathPlayer moveToPoint:CGPointMake(halfTank, halfTank)];
114 | [self.pathPlayer addLineToPoint:CGPointMake( halfTank, -halfTank)];
115 | [self.pathPlayer addLineToPoint:CGPointMake(-halfTank, -halfTank)];
116 | [self.pathPlayer addLineToPoint:CGPointMake(-halfTank, -halfTank + paddingBarrel)];
117 | [self.pathPlayer addLineToPoint:CGPointMake(-halfTank - sizeBarrel, -halfTank + paddingBarrel)];
118 | [self.pathPlayer addLineToPoint:CGPointMake(-halfTank - sizeBarrel, -halfTank + paddingBarrel+ sizeBarrel)];
119 | [self.pathPlayer addLineToPoint:CGPointMake(-halfTank, -halfTank + paddingBarrel+ sizeBarrel)];
120 | [self.pathPlayer addLineToPoint:CGPointMake(-halfTank, halfTank)];
121 | [self.pathPlayer closePath];
122 |
123 | [self.nodePlayer setPath:self.pathPlayer.CGPath];
124 | [self.nodePlayer setPosition:CGPointMake(size.width-halfTank, size.height - halfTank)];
125 | [self.nodePlayer setPhysicsBody:[SKPhysicsBody bodyWithPolygonFromPath:self.pathPlayer.CGPath]];
126 | [self.nodePlayer.physicsBody setCategoryBitMask:MASK_PLAYER];
127 | [self.nodePlayer.physicsBody setAllowsRotation:FALSE];
128 |
129 | CGRect rectBullet = CGRectMake(-sizeBarrel/2, -sizeBarrel/2, sizeBarrel, sizeBarrel);
130 | [self setPathBullet:[UIBezierPath bezierPathWithOvalInRect:rectBullet]];
131 |
132 | [self setPathEnemy:[UIBezierPath new]];
133 | [self.pathEnemy moveToPoint:CGPointMake(-halfTank, halfTank)];
134 | [self.pathEnemy addLineToPoint:CGPointMake(-halfTank, -halfTank)];
135 | [self.pathEnemy addLineToPoint:CGPointMake(halfTank-paddingBarrel, -halfTank)];
136 | [self.pathEnemy addLineToPoint:CGPointMake(halfTank-paddingBarrel, -halfTank + paddingBarrel)];
137 | [self.pathEnemy addLineToPoint:CGPointMake(halfTank, -halfTank + paddingBarrel)];
138 | [self.pathEnemy addLineToPoint:CGPointMake(halfTank, halfTank - paddingBarrel)];
139 | [self.pathEnemy addLineToPoint:CGPointMake(halfTank-paddingBarrel, halfTank - paddingBarrel)];
140 | [self.pathEnemy addLineToPoint:CGPointMake(halfTank-paddingBarrel, halfTank)];
141 | [self.pathEnemy closePath];
142 |
143 | }
144 |
145 | - (void)update:(NSTimeInterval)currentTime {
146 | [super update:currentTime];
147 |
148 | if (!self.isGameRunning) {
149 | return;
150 | }
151 |
152 | CGSize sizeScene = self.size;
153 | CGFloat sizeTank = sizeScene.height / TANK_ROW_NUM;
154 | CGFloat sizeBarrel = sizeTank * TANK_BARREL_RATIO;
155 |
156 | if (self.nodeEnemy == nil || self.nodeEnemy.position.x > self.spaceEnemy + sizeTank) {
157 | [self setNodeEnemy:[SKShapeNode node]];
158 | [self.nodeEnemy setName:NAME_TANK];
159 | [self.nodeEnemy setFillColor:self.colorAccent];
160 | [self.nodeEnemy setPosition:CGPointMake(sizeTank/2 + 5, sizeTank*(0.5 + arc4random()%TANK_ROW_NUM))];
161 | [self.nodeEnemy setPath:self.pathEnemy.CGPath];
162 | [self.nodeEnemy setPhysicsBody:[SKPhysicsBody bodyWithPolygonFromPath:self.pathEnemy.CGPath]];
163 | [self.nodeEnemy.physicsBody setFriction:0];
164 | [self.nodeEnemy.physicsBody setLinearDamping:0];
165 | [self.nodeEnemy.physicsBody setAllowsRotation:FALSE];
166 | [self.nodeEnemy.physicsBody setUsesPreciseCollisionDetection:TRUE];
167 | [self.nodeEnemy.physicsBody setCategoryBitMask:MASK_TANK];
168 | [self.nodeEnemy.physicsBody setContactTestBitMask:MASK_WORLD|MASK_PLAYER];
169 | [self addChild:self.nodeEnemy];
170 | [self.nodeEnemy.physicsBody applyImpulse:CGVectorMake(2, 0)];
171 | }
172 |
173 | if (self.nodeBullet.position.x < sizeScene.width - sizeTank - sizeBarrel - self.spaceBullet) {
174 |
175 | CGPoint pt = self.nodePlayer.position;
176 |
177 | [self setNodeBullet:[SKShapeNode node]];
178 | [self.nodeBullet setName:NAME_BULLET];
179 | [self.nodeBullet setFillColor:self.colorAccent];
180 | [self.nodeBullet setPosition:CGPointMake(pt.x - sizeTank - sizeBarrel/3, pt.y)];
181 | [self.nodeBullet setPath:self.pathBullet.CGPath];
182 | [self.nodeBullet setPhysicsBody:[SKPhysicsBody bodyWithCircleOfRadius:sizeBarrel/2]];
183 | [self.nodeBullet.physicsBody setFriction:0];
184 | [self.nodeBullet.physicsBody setLinearDamping:0];
185 | [self.nodeBullet.physicsBody setAllowsRotation:FALSE];
186 | [self.nodeBullet.physicsBody setUsesPreciseCollisionDetection:TRUE];
187 | [self.nodeBullet.physicsBody setCategoryBitMask:MASK_BULLET];
188 | [self.nodeBullet.physicsBody setContactTestBitMask:MASK_WORLD|MASK_TANK];
189 | [self addChild:self.nodeBullet];
190 | [self.nodeBullet.physicsBody applyImpulse:CGVectorMake(-1, 0)];
191 | }
192 | }
193 |
194 | - (void)reset {
195 | [self setNodeEnemy:nil];
196 | [self setNodeBullet:nil];
197 | [self setSpaceEnemy:TANK_NUM_SPACING];
198 | [self setSpaceBullet:BULLET_NUM_SPACING];
199 |
200 | CGSize sizeScene = self.size;
201 | CGFloat sizeTank = sizeScene.height / TANK_ROW_NUM;
202 | CGFloat halfTank = sizeTank / 2;
203 | [self.nodePlayer setPosition:CGPointMake(sizeScene.width-halfTank, sizeScene.height - halfTank)];
204 |
205 | [self enumerateChildNodesWithName:NAME_TANK usingBlock:^(SKNode* node, BOOL* stop) {
206 | [node removeFromParent];
207 | }];
208 | [self enumerateChildNodesWithName:NAME_BULLET usingBlock:^(SKNode* node, BOOL* stop) {
209 | [node removeFromParent];
210 | }];
211 | }
212 |
213 | - (void)start {
214 | [self setIsGameRunning:TRUE];
215 | }
216 |
217 | - (void)moveHandle:(CGFloat)percent {
218 | CGSize sizeScene = self.size;
219 | CGFloat sizeTank = sizeScene.height / TANK_ROW_NUM;
220 | // CGFloat halfTank = sizeTank / 2;
221 | // CGFloat sizeBarrel = sizeTank * TANK_BARREL_RATIO;
222 | // CGFloat paddingBarrel = (sizeTank - sizeBarrel)/2;
223 |
224 | CGPoint old = self.nodePlayer.position;
225 | CGFloat y = (sizeScene.height - sizeTank)*(1-percent) + sizeTank/2;
226 | CGPoint new = CGPointMake(old.x, y);
227 | [self.nodePlayer setPosition:new];
228 | }
229 |
230 | - (void)didEndContact:(SKPhysicsContact *)contact {
231 |
232 | if (contact.bodyB.node == nil || contact.bodyA.node == nil) {
233 |
234 | return;
235 | }
236 | NSArray* array = @[contact.bodyA.node, contact.bodyB.node];
237 | for (SKNode* node in array) {
238 | if (node == self.nodeBullet) {
239 | self.nodeBullet = nil;
240 | } else if (node == self.nodeEnemy) {
241 | self.nodeEnemy = nil;
242 | } else if (node == self.nodePlayer) {
243 | [self gameOver];
244 | }
245 | if (node != self && node != self.nodePlayer) {
246 | [node removeFromParent];
247 | }
248 | }
249 | //
250 | // if (self.isGameWin) {
251 | // [self gameWin];
252 | // }
253 | }
254 |
255 | - (void)gameWin {
256 | [super gameWin];
257 | [self setIsGameRunning:FALSE];
258 | }
259 |
260 | - (void)gameOver {
261 | [super gameOver];
262 | [self setIsGameRunning:FALSE];
263 | }
264 |
265 | - (BOOL)isGameWin {
266 | // __block NSUInteger count = 0;
267 | // [self enumerateChildNodesWithName:NAME_BLOCK usingBlock:^(SKNode* node, BOOL* stop) {
268 | // count = count + 1;
269 | // }];
270 | // return count == 0;
271 | return FALSE;
272 | }
273 |
274 |
275 | @end
276 |
--------------------------------------------------------------------------------
/SmartRefreshDemo/Demo/UIDemoFooterController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIDemoFooterController.swift
3 | // Refresh
4 | //
5 | // Created by SCWANG on 2021/7/5.
6 | // Copyright © 2021 Teeyun. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class UIDemoFooterController: UITableViewController {
12 |
13 | class Item {
14 | let name: String
15 | let detail: String?
16 | let block: (() -> Void)?
17 | init(_ name: String, _ detail: String? = nil, _ block: (() -> Void)? = nil) {
18 | self.name = name;
19 | self.block = block;
20 | self.detail = detail;
21 | }
22 | }
23 |
24 | class Group {
25 | let title: String
26 | var items: [Item]
27 | init(_ title: String, _ items: [Item]) {
28 | self.title = title;
29 | self.items = items;
30 | }
31 | }
32 |
33 | override init(style: UITableView.Style) {
34 | super.init(style: .grouped)
35 | }
36 |
37 | required init?(coder: NSCoder) {
38 | super.init(coder: coder)
39 | }
40 |
41 | var colorAccent: UIColor = UIColor.white
42 | var colorPrimary: UIColor = UIColor.clear
43 | var navTranslucent: Bool = true;
44 | var navTintColor: UIColor = UIColor.black;
45 | var navBarTintColor: UIColor? = UIColor.white;
46 | var navBackgroundColor: UIColor? = UIColor.white;
47 | var navTitleTextAttributes: [NSAttributedString.Key : Any]? = nil
48 |
49 | lazy var groups: [Group] = {
50 | return self.initGroupItems();
51 | }()
52 |
53 | lazy var footer: UIRefreshFooter = {
54 | let footer = self.initRefreshFooter();
55 | self.colorAccent = footer.colorAccent
56 | self.colorPrimary = footer.colorPrimary
57 | return footer;
58 | }()
59 |
60 | func initRefreshFooter() -> UIRefreshFooter {
61 | return UIRefreshClassicsFooter.init();
62 | }
63 |
64 | func initGroupItems() -> [Group]{
65 | return [
66 | Group(" ", [
67 | Item("默认主题", "点击还原") {
68 | self.changeTheme(self.colorPrimary, self.colorAccent)
69 | },
70 | Item("橙色主题", "点击切换") {
71 | self.changeTheme(UIColor.systemOrange, UIColor.white)
72 | },
73 | Item("红色主题", "点击切换") {
74 | self.changeTheme(UIColor.systemRed, UIColor.white)
75 | },
76 | Item("绿色主题", "点击切换") {
77 | self.changeTheme(UIColor.systemGreen, UIColor.white)
78 | },
79 | Item("蓝色主题", "点击切换") {
80 | self.changeTheme(UIColor.systemBlue, UIColor.white)
81 | },
82 | ]),
83 | Group("测试数据", [
84 | Item("测试数据1"),
85 | Item("测试数据2"),
86 | Item("测试数据3"),
87 | Item("测试数据4"),
88 | Item("测试数据5"),
89 | Item("测试数据6"),
90 | Item("测试数据7"),
91 | Item("测试数据8"),
92 | ])
93 | ]
94 | }
95 |
96 | func changeTheme(_ primary: UIColor, _ accent: UIColor) {
97 | self.footer.colorAccent = accent;
98 | self.footer.colorPrimary = primary;
99 | // self.footer.beginRefresh();
100 | guard let nav = self.navigationController?.navigationBar else {
101 | return
102 | }
103 | if primary == self.colorPrimary && accent == self.colorAccent {
104 | nav.isTranslucent = self.navTranslucent
105 | nav.tintColor = self.navTintColor
106 | nav.barTintColor = self.navBarTintColor
107 | nav.backgroundColor = self.navBackgroundColor
108 | nav.titleTextAttributes = self.navTitleTextAttributes
109 | } else {
110 | nav.isTranslucent = false;
111 | nav.tintColor = accent;
112 | nav.barTintColor = primary;
113 | nav.backgroundColor = primary;
114 | nav.titleTextAttributes = [NSAttributedString.Key.foregroundColor:accent];
115 | }
116 | self.setNeedsStatusBarAppearanceUpdate()
117 | }
118 |
119 | override var preferredStatusBarStyle: UIStatusBarStyle {
120 | return self.navigationController?.navigationBar.isTranslucent ?? false ? .default : .lightContent;
121 | }
122 |
123 | // override var childForStatusBarStyle: UIViewController? {
124 | // return self.top
125 | // }
126 |
127 | override func viewDidLoad() {
128 | super.viewDidLoad()
129 | self.title = (type(of: self)).description()
130 | .replacingOccurrences(of: "UI", with: "")
131 | .replacingOccurrences(of: "SmartRefreshDemo.", with: "")
132 | //.replacingOccurrences(of: "Footer", with: "")
133 | .replacingOccurrences(of: "Controller", with: "")
134 |
135 | self.footer.attach(self.tableView);
136 | self.footer.addTarget(self, action: #selector(onLoadMore), for: .valueChanged)
137 | // self.footer.beginLoadMore()
138 | // Uncomment the following line to preserve selection between presentations
139 | // self.clearsSelectionOnViewWillAppear = false
140 |
141 | // Uncomment the following line to display an Edit button in the navigation bar for this view controller.
142 | // self.navigationItem.rightBarButtonItem = self.editButtonItem
143 | }
144 |
145 | override func viewWillAppear(_ animated: Bool) {
146 | super.viewWillAppear(animated);
147 | guard let nav = self.navigationController?.navigationBar else {
148 | return
149 | }
150 | self.navTranslucent = nav.isTranslucent;
151 | self.navTintColor = nav.tintColor;
152 | self.navBarTintColor = nav.barTintColor;
153 | self.navBackgroundColor = nav.backgroundColor;
154 | self.navTitleTextAttributes = nav.titleTextAttributes;
155 | }
156 |
157 | override func viewWillDisappear(_ animated: Bool) {
158 | super.viewWillDisappear(animated);
159 | guard let nav = self.navigationController?.navigationBar else {
160 | return
161 | }
162 | nav.isTranslucent = self.navTranslucent
163 | nav.tintColor = self.navTintColor
164 | nav.barTintColor = self.navBarTintColor
165 | nav.backgroundColor = self.navBackgroundColor
166 | nav.titleTextAttributes = self.navTitleTextAttributes
167 | }
168 |
169 | @objc func onLoadMore() {
170 | DispatchQueue.main.asyncAfter(deadline: .now() + 2.5) {
171 | let lastGroup = self.groups[self.groups.count - 1];
172 | let items = lastGroup.items;
173 | lastGroup.items = items + items;
174 | self.tableView.reloadData();
175 | if (lastGroup.items.count <= 16) {
176 | self.footer.finishLoadMore();
177 | } else {
178 | self.footer.finishLoadMoreWithNoMoreData();
179 | }
180 | }
181 | }
182 |
183 | // MARK: - Table view data source
184 |
185 | override func numberOfSections(in tableView: UITableView) -> Int {
186 | // #warning Incomplete implementation, return the number of sections
187 | return groups.count
188 | }
189 |
190 | override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
191 | // #warning Incomplete implementation, return the number of rows
192 | return groups[section].items.count;
193 | }
194 |
195 | override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
196 |
197 | return section == 0 ? 0.001 : 25
198 | // return super.tableView(tableView, heightForHeaderInSection: section);
199 | }
200 |
201 | override func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
202 | return section == self.groups.count - 1 ? 0.001: 15;
203 | }
204 |
205 | override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
206 | return groups[section].title;
207 | }
208 |
209 | override func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? {
210 | return " ";//groups[section].title;
211 | }
212 |
213 | override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
214 | // let cell = tableView.dequeueReusableCell(withIdentifier: "item", for: indexPath)
215 |
216 | let cell = tableView.dequeueReusableCell(withIdentifier: "item")
217 | ?? UITableViewCell.init(style: .value1, reuseIdentifier: "item")
218 |
219 | // Configure the cell...
220 | cell.textLabel?.text = self.groups[indexPath.section].items[indexPath.row].name;
221 |
222 | cell.detailTextLabel?.text = self.groups[indexPath.section].items[indexPath.row].detail;
223 |
224 | return cell
225 | }
226 |
227 | override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
228 | tableView.deselectRow(at: indexPath, animated: true);
229 | self.groups[indexPath.section].items[indexPath.row].block?()
230 | }
231 | /*
232 | // Override to support conditional editing of the table view.
233 | override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
234 | // Return false if you do not want the specified item to be editable.
235 | return true
236 | }
237 | */
238 |
239 | /*
240 | // Override to support editing the table view.
241 | override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
242 | if editingStyle == .delete {
243 | // Delete the row from the data source
244 | tableView.deleteRows(at: [indexPath], with: .fade)
245 | } else if editingStyle == .insert {
246 | // Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view
247 | }
248 | }
249 | */
250 |
251 | /*
252 | // Override to support rearranging the table view.
253 | override func tableView(_ tableView: UITableView, moveRowAt fromIndexPath: IndexPath, to: IndexPath) {
254 |
255 | }
256 | */
257 |
258 | /*
259 | // Override to support conditional rearranging of the table view.
260 | override func tableView(_ tableView: UITableView, canMoveRowAt indexPath: IndexPath) -> Bool {
261 | // Return false if you do not want the item to be re-orderable.
262 | return true
263 | }
264 | */
265 |
266 | /*
267 | // MARK: - Navigation
268 |
269 | // In a storyboard-based application, you will often want to do a little preparation before navigation
270 | override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
271 | // Get the new view controller using segue.destination.
272 | // Pass the selected object to the new view controller.
273 | }
274 | */
275 |
276 |
277 | }
278 |
--------------------------------------------------------------------------------
/SmartRefreshControl/Vector/PathElement.m:
--------------------------------------------------------------------------------
1 | //
2 | // PathElement.m
3 | // Refresh
4 | //
5 | // Created by Teeyun on 2020/9/8.
6 | // Copyright © 2020 Teeyun. All rights reserved.
7 | //
8 |
9 | #import "PathElement.h"
10 | #import "PathsParser.h"
11 | #import "Utilities.h"
12 |
13 | @interface PathElement ()
14 |
15 | - (void) parseData:(NSString *)data;
16 |
17 | @property (nonatomic, readwrite) CGPathRef pathForShape;
18 |
19 | @end
20 |
21 | @implementation PathElement
22 |
23 | - (void)dealloc
24 | {
25 | if (self.pathForShape) {
26 | CGPathRelease(self.pathForShape);
27 | }
28 | }
29 |
30 | + (instancetype)newWith:(NSString *)data {
31 | PathElement* this = [self new];
32 | if (this) {
33 | [this parseData:data];
34 | }
35 | return this;
36 | }
37 |
38 | - (void)parseData:(NSString *)data
39 | {
40 | CGMutablePathRef path = CGPathCreateMutable();
41 | NSScanner* dataScanner = [NSScanner scannerWithString:data];
42 | Curve lastCurve = [PathsParser startingCurve];
43 | BOOL foundCmd;
44 |
45 | NSCharacterSet *knownCommands = [NSCharacterSet characterSetWithCharactersInString:@"MmLlCcVvHhAaSsQqTtZz"];
46 | NSString* command;
47 |
48 | do {
49 |
50 | command = nil;
51 | foundCmd = [dataScanner scanCharactersFromSet:knownCommands intoString:&command];
52 |
53 | if (command.length > 1) {
54 | // Take only one char (it can happen that multiple commands are consecutive, as "ZM" - so we only want to get the "Z")
55 | const NSUInteger tooManyChars = command.length-1;
56 | command = [command substringToIndex:1];
57 | [dataScanner setScanLocation:([dataScanner scanLocation] - tooManyChars)];
58 | }
59 |
60 | if (foundCmd) {
61 | if ([@"z" isEqualToString:command] || [@"Z" isEqualToString:command]) {
62 | lastCurve = [PathsParser readCloseCommand:[NSScanner scannerWithString:command]
63 | path:path
64 | relativeTo:lastCurve.p];
65 | } else {
66 | NSString* cmdArgs = nil;
67 | BOOL foundParameters = [dataScanner scanUpToCharactersFromSet:knownCommands
68 | intoString:&cmdArgs];
69 |
70 | if (foundParameters) {
71 | NSString* cmdWithParams = [command stringByAppendingString:cmdArgs];
72 | NSScanner* cmdScanner = [NSScanner scannerWithString:cmdWithParams];
73 |
74 | if ([@"m" isEqualToString:command]) {
75 | lastCurve = [PathsParser readMovetoDrawToCmdGroups:cmdScanner
76 | path:path
77 | relativeTo:lastCurve.p
78 | isRelative:TRUE];
79 | } else if ([@"M" isEqualToString:command]) {
80 | lastCurve = [PathsParser readMovetoDrawToCmdGroups:cmdScanner
81 | path:path
82 | relativeTo:CGPointZero
83 | isRelative:FALSE];
84 | } else if ([@"l" isEqualToString:command]) {
85 | lastCurve = [PathsParser readLineToCmd:cmdScanner
86 | path:path
87 | relativeTo:lastCurve.p
88 | isRelative:TRUE];
89 | } else if ([@"L" isEqualToString:command]) {
90 | lastCurve = [PathsParser readLineToCmd:cmdScanner
91 | path:path
92 | relativeTo:CGPointZero
93 | isRelative:FALSE];
94 | } else if ([@"v" isEqualToString:command]) {
95 | lastCurve = [PathsParser readVerticalLineToCmd:cmdScanner
96 | path:path
97 | relativeTo:lastCurve.p];
98 | } else if ([@"V" isEqualToString:command]) {
99 | lastCurve = [PathsParser readVerticalLineToCmd:cmdScanner
100 | path:path
101 | relativeTo:CGPointZero];
102 | } else if ([@"h" isEqualToString:command]) {
103 | lastCurve = [PathsParser readHorizontalLineToCmd:cmdScanner
104 | path:path
105 | relativeTo:lastCurve.p];
106 | } else if ([@"H" isEqualToString:command]) {
107 | lastCurve = [PathsParser readHorizontalLineToCmd:cmdScanner
108 | path:path
109 | relativeTo:CGPointZero];
110 | } else if ([@"c" isEqualToString:command]) {
111 | lastCurve = [PathsParser readCurveToCmd:cmdScanner
112 | path:path
113 | relativeTo:lastCurve.p
114 | isRelative:TRUE];
115 | } else if ([@"C" isEqualToString:command]) {
116 | lastCurve = [PathsParser readCurveToCmd:cmdScanner
117 | path:path
118 | relativeTo:CGPointZero
119 | isRelative:FALSE];
120 | } else if ([@"s" isEqualToString:command]) {
121 | lastCurve = [PathsParser readSmoothCurveToCmd:cmdScanner
122 | path:path
123 | relativeTo:lastCurve.p
124 | withPrevCurve:lastCurve
125 | isRelative:TRUE];
126 | } else if ([@"S" isEqualToString:command]) {
127 | lastCurve = [PathsParser readSmoothCurveToCmd:cmdScanner
128 | path:path
129 | relativeTo:CGPointZero
130 | withPrevCurve:lastCurve
131 | isRelative:FALSE];
132 | } else if ([@"q" isEqualToString:command]) {
133 | lastCurve = [PathsParser readQuadraticCurveToCmd:cmdScanner
134 | path:path
135 | relativeTo:lastCurve.p
136 | isRelative:TRUE];
137 | } else if ([@"Q" isEqualToString:command]) {
138 | lastCurve = [PathsParser readQuadraticCurveToCmd:cmdScanner
139 | path:path
140 | relativeTo:CGPointZero
141 | isRelative:FALSE];
142 | } else if ([@"t" isEqualToString:command]) {
143 | lastCurve = [PathsParser readSmoothQuadraticCurveToCmd:cmdScanner
144 | path:path
145 | relativeTo:lastCurve.p
146 | withPrevCurve:lastCurve];
147 | } else if ([@"T" isEqualToString:command]) {
148 | lastCurve = [PathsParser readSmoothQuadraticCurveToCmd:cmdScanner
149 | path:path
150 | relativeTo:CGPointZero
151 | withPrevCurve:lastCurve];
152 | } else if ([@"a" isEqualToString:command]) {
153 | lastCurve = [PathsParser readEllipticalArcArguments:cmdScanner
154 | path:path
155 | relativeTo:lastCurve.p
156 | isRelative:TRUE];
157 | } else if ([@"A" isEqualToString:command]) {
158 | lastCurve = [PathsParser readEllipticalArcArguments:cmdScanner
159 | path:path
160 | relativeTo:CGPointZero
161 | isRelative:FALSE];
162 | } else {
163 | //SVGKitLogWarn(@"unsupported command %@", command);
164 | }
165 | }
166 | }
167 | }
168 |
169 | } while (foundCmd);
170 |
171 |
172 | self.pathForShape = path;
173 | self.viewport = CGPathGetBoundingBox(path);
174 | self.changed = TRUE;
175 | //CGPathRelease(path);
176 | }
177 |
178 | - (CALayer *) newLayer
179 | {
180 | NSAssert(self.pathForShape != NULL, @"Requested a CALayer for SVG shape that never initialized its own .pathForShapeInRelativeCoords property. Shape class = %@. Shape instance = %@", [self class], self );
181 |
182 | return [Utilities newLayerForElement:self withPath:self.pathForShape];
183 | }
184 |
185 | @end
186 |
--------------------------------------------------------------------------------