├── .gitignore ├── LICENSE ├── README.md ├── SmartRefresh.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist ├── xcshareddata │ └── xcschemes │ │ └── SmartRefreshDemo.xcscheme └── xcuserdata │ └── scwang.xcuserdatad │ └── xcschemes │ └── xcschememanagement.plist ├── SmartRefreshControl.podspec ├── SmartRefreshControl ├── Component │ ├── UIRefreshComponent.h │ ├── UIRefreshComponent.m │ ├── UIRefreshFooter.h │ ├── UIRefreshFooter.m │ ├── UIRefreshHeader.h │ ├── UIRefreshHeader.m │ ├── UIVectorView.h │ └── UIVectorView.m ├── Fly │ ├── FlyView.h │ ├── FlyView.m │ ├── MountainView.h │ └── MountainView.m ├── Footer │ ├── UIRefreshClassicsFooter.h │ └── UIRefreshClassicsFooter.m ├── Game │ ├── UIRefreshGameHeader.h │ ├── UIRefreshGameHeader.m │ ├── UIRefreshGamePlayingScene.h │ ├── UIRefreshGamePlayingScene.m │ ├── UIRefreshGameStartScene.h │ └── UIRefreshGameStartScene.m ├── Header │ ├── UIRefreshBezierCircleHeader.h │ ├── UIRefreshBezierCircleHeader.m │ ├── UIRefreshBezierRadarHeader.h │ ├── UIRefreshBezierRadarHeader.m │ ├── UIRefreshClassicsHeader.h │ ├── UIRefreshClassicsHeader.m │ ├── UIRefreshDeliveryHeader.h │ ├── UIRefreshDeliveryHeader.m │ ├── UIRefreshDropBoxHeader.h │ ├── UIRefreshDropBoxHeader.m │ ├── UIRefreshFlyHeader.h │ ├── UIRefreshFlyHeader.m │ ├── UIRefreshGameBattleCityHeader.h │ ├── UIRefreshGameBattleCityHeader.m │ ├── UIRefreshGameHitBlockHeader.h │ ├── UIRefreshGameHitBlockHeader.m │ ├── UIRefreshMaterialHeader.h │ ├── UIRefreshMaterialHeader.m │ ├── UIRefreshOriginalHeader.h │ ├── UIRefreshOriginalHeader.m │ ├── UIRefreshPhoenixHeader.h │ ├── UIRefreshPhoenixHeader.m │ ├── UIRefreshStoreHouseHeader.h │ ├── UIRefreshStoreHouseHeader.m │ ├── UIRefreshTaurusHeader.h │ ├── UIRefreshTaurusHeader.m │ ├── UIRefreshWaveSwipeHeader.h │ └── UIRefreshWaveSwipeHeader.m ├── Info.plist ├── SmartRefreshControl.h ├── StoreHouse │ ├── StoreHouseLine.h │ ├── StoreHouseLine.m │ ├── StoreHousePath.h │ └── StoreHousePath.m └── Vector │ ├── Element.h │ ├── Element.m │ ├── PathElement.h │ ├── PathElement.m │ ├── PathsParser.h │ ├── PathsParser.m │ ├── Utilities.h │ ├── Utilities.m │ ├── ValueAnimator.h │ ├── ValueAnimator.m │ ├── VectorImage.h │ └── VectorImage.m ├── SmartRefreshDemo ├── AppDelegate.swift ├── Assets.xcassets │ ├── AccentColor.colorset │ │ └── Contents.json │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── Contents.json ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── Demo │ ├── DemoViewController.swift │ ├── Footer │ │ └── UIClassicsFooterController.swift │ ├── Header │ │ ├── UIBezierCircleController.swift │ │ ├── UIBezierRadarController.swift │ │ ├── UIClassicsController.swift │ │ ├── UIDeliveryController.swift │ │ ├── UIDropBoxController.swift │ │ ├── UIFlyController.swift │ │ ├── UIGameBattleCityController.swift │ │ ├── UIGameHitBlockController.swift │ │ ├── UIMaterialController.swift │ │ ├── UIOriginalController.swift │ │ ├── UIPhoenixController.swift │ │ ├── UIStoreHouseController.swift │ │ ├── UITaurusController.swift │ │ └── UIWaveSwipeController.swift │ ├── UIDemoFooterController.swift │ └── UIDemoHeaderController.swift ├── Info.plist ├── NavigationViewController.swift ├── SceneDelegate.swift ├── SmartRefreshDemo-Bridging-Header.h └── ViewController.swift └── art └── gif ├── header-circle.gif ├── header-classics.gif ├── header-delivery.gif ├── header-drop.gif ├── header-fly.gif ├── header-game-block.gif ├── header-game-tank.gif ├── header-material.gif ├── header-original.gif ├── header-phoenix.gif ├── header-radar.gif ├── header-store.gif ├── header-taurus.gif └── header-wave.gif /.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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # IOS 智能下拉刷新框架 - SmartRefreshControl 2 | 3 | [![License](https://img.shields.io/badge/License%20-Apache%202-337ab7.svg)](https://www.apache.org/licenses/LICENSE-2.0) 4 | [![Cocoapods](https://img.shields.io/badge/Pod%20-%20SmartRefresh-4cae4c.svg)](https://www.cocoapods.org/) 5 | [![Platform](https://img.shields.io/badge/Platform-IOS-f0ad4e.svg)](https://developer.apple.com/) 6 | [![Author](https://img.shields.io/badge/Author-scwang90-11bbff.svg)](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 | |![](art/gif/header-delivery.gif)|![](art/gif/header-material.gif)| 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 | |![](art/gif/header-radar.gif)|![](art/gif/header-circle.gif)| 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 | |![](art/gif/header-fly.gif)|![](art/gif/header-drop.gif)| 65 | |[FlyRefresh](https://github.com/race604/FlyRefresh)|[DropBoxHeader](#1)| 66 | 67 | |Phoenix|Taurus| 68 | |:---:|:---:| 69 | |![](art/gif/header-phoenix.gif)|![](art/gif/header-taurus.gif)| 70 | |[Yalantis/Phoenix](https://github.com/Yalantis/Phoenix)|[Yalantis/Taurus](https://github.com/Yalantis/Taurus) 71 | 72 | |BattleCity|HitBlock| 73 | |:---:|:---:| 74 | |![](art/gif/header-game-tank.gif)|![](art/gif/header-game-block.gif)| 75 | |[FunGame/BattleCity](https://github.com/Hitomis/FunGameRefresh)|[FunGame/HitBlock](https://github.com/Hitomis/FunGameRefresh) 76 | 77 | 78 | |StoreHouse|WaveSwipe| 79 | |:---:|:---:| 80 | |![](art/gif/header-store.gif)|![](art/gif/header-wave.gif)| 81 | |[CRefreshLayout](https://github.com/cloay/CRefreshLayout)|[WaveSwipeRefreshLayout](https://github.com/recruit-lifestyle/WaveSwipeRefreshLayout) 82 | 83 | 84 | |Original|Classics| 85 | |:---:|:---:| 86 | |![](art/gif/header-original.gif)|![](art/gif/header-classics.gif)| 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. -------------------------------------------------------------------------------- /SmartRefresh.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /SmartRefresh.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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.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 | -------------------------------------------------------------------------------- /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/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/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/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/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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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/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 | -------------------------------------------------------------------------------- /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/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/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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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/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/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/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 | -------------------------------------------------------------------------------- /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/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/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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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/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/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/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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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/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/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/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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /SmartRefreshDemo/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /art/gif/header-circle.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scwang90/SmartRefreshControl/9319448b7080e0940b6906b4323e508675a4b24d/art/gif/header-circle.gif -------------------------------------------------------------------------------- /art/gif/header-classics.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scwang90/SmartRefreshControl/9319448b7080e0940b6906b4323e508675a4b24d/art/gif/header-classics.gif -------------------------------------------------------------------------------- /art/gif/header-delivery.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scwang90/SmartRefreshControl/9319448b7080e0940b6906b4323e508675a4b24d/art/gif/header-delivery.gif -------------------------------------------------------------------------------- /art/gif/header-drop.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scwang90/SmartRefreshControl/9319448b7080e0940b6906b4323e508675a4b24d/art/gif/header-drop.gif -------------------------------------------------------------------------------- /art/gif/header-fly.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scwang90/SmartRefreshControl/9319448b7080e0940b6906b4323e508675a4b24d/art/gif/header-fly.gif -------------------------------------------------------------------------------- /art/gif/header-game-block.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scwang90/SmartRefreshControl/9319448b7080e0940b6906b4323e508675a4b24d/art/gif/header-game-block.gif -------------------------------------------------------------------------------- /art/gif/header-game-tank.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scwang90/SmartRefreshControl/9319448b7080e0940b6906b4323e508675a4b24d/art/gif/header-game-tank.gif -------------------------------------------------------------------------------- /art/gif/header-material.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scwang90/SmartRefreshControl/9319448b7080e0940b6906b4323e508675a4b24d/art/gif/header-material.gif -------------------------------------------------------------------------------- /art/gif/header-original.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scwang90/SmartRefreshControl/9319448b7080e0940b6906b4323e508675a4b24d/art/gif/header-original.gif -------------------------------------------------------------------------------- /art/gif/header-phoenix.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scwang90/SmartRefreshControl/9319448b7080e0940b6906b4323e508675a4b24d/art/gif/header-phoenix.gif -------------------------------------------------------------------------------- /art/gif/header-radar.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scwang90/SmartRefreshControl/9319448b7080e0940b6906b4323e508675a4b24d/art/gif/header-radar.gif -------------------------------------------------------------------------------- /art/gif/header-store.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scwang90/SmartRefreshControl/9319448b7080e0940b6906b4323e508675a4b24d/art/gif/header-store.gif -------------------------------------------------------------------------------- /art/gif/header-taurus.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scwang90/SmartRefreshControl/9319448b7080e0940b6906b4323e508675a4b24d/art/gif/header-taurus.gif -------------------------------------------------------------------------------- /art/gif/header-wave.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scwang90/SmartRefreshControl/9319448b7080e0940b6906b4323e508675a4b24d/art/gif/header-wave.gif --------------------------------------------------------------------------------