├── .gitignore ├── .swiftpm └── xcode │ └── package.xcworkspace │ └── contents.xcworkspacedata ├── 368DED3D-4B49-4487-8E45-F2BB39D190B3.jpeg ├── 64DAA174-C657-4A95-B418-1DA7484EFE3F.jpeg ├── EasyListView.podspec ├── EasyListViewExample.xcodeproj ├── project.pbxproj └── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── EasyListViewExample.xcworkspace ├── contents.xcworkspacedata └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── EasyListViewExample ├── AppDelegate.swift ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ ├── Contents.json │ ├── avatar.imageset │ │ ├── 054.00.png │ │ └── Contents.json │ └── indicator.imageset │ │ ├── Contents.json │ │ ├── indicator@2x.png │ │ └── indicator@3x.png ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── Demo1 │ └── FormListController.swift ├── Demo2 │ ├── UserCell.swift │ └── UserListController.swift ├── EasyListViewExample-Bridging-Header.h ├── Info.plist ├── Main │ ├── EasyListCell.swift │ ├── MainViewController.swift │ ├── ObjCTestController.h │ └── ObjCTestController.m └── Vendor │ ├── ESPullToRefresh │ ├── Animator │ │ ├── Base.lproj │ │ │ └── Localizable.strings │ │ ├── ESRefreshFooterAnimator.swift │ │ ├── ESRefreshHeaderAnimator.swift │ │ ├── icon_pull_to_refresh_arrow@2x.png │ │ ├── zh-Hans.lproj │ │ │ └── Localizable.strings │ │ └── zh-Hant.lproj │ │ │ └── Localizable.strings │ ├── ES.swift │ ├── ESPullToRefresh+Manager.swift │ ├── ESPullToRefresh.swift │ ├── ESRefreshAnimator.swift │ ├── ESRefreshComponent.swift │ └── ESRefreshProtocol.swift │ └── Stevia │ ├── Stevia+Alignment.swift │ ├── Stevia+Baselines.swift │ ├── Stevia+Center.swift │ ├── Stevia+Constraints.swift │ ├── Stevia+Content.swift │ ├── Stevia+DoubleDash.swift │ ├── Stevia+Equation.swift │ ├── Stevia+Fill.swift │ ├── Stevia+FlexibleMargin.swift │ ├── Stevia+GetConstraint.swift │ ├── Stevia+Hierarchy.swift │ ├── Stevia+LayoutAnchors.swift │ ├── Stevia+Notifications.swift │ ├── Stevia+Operators.swift │ ├── Stevia+Percentage.swift │ ├── Stevia+Position.swift │ ├── Stevia+Size.swift │ ├── Stevia+Stacks.swift │ └── Stevia+Style.swift ├── Kapture1.gif ├── Kapture2.gif ├── LICENSE ├── Package.swift ├── Podfile ├── Podfile.lock ├── Pods ├── EasyCompatible │ ├── LICENSE │ ├── README.md │ └── Sources │ │ └── EasyCompatible.swift ├── Local Podspecs │ └── EasyListView.podspec.json ├── Manifest.lock ├── Pods.xcodeproj │ └── project.pbxproj └── Target Support Files │ ├── EasyCompatible │ ├── EasyCompatible-Info.plist │ ├── EasyCompatible-dummy.m │ ├── EasyCompatible-prefix.pch │ ├── EasyCompatible-umbrella.h │ ├── EasyCompatible.debug.xcconfig │ ├── EasyCompatible.modulemap │ └── EasyCompatible.release.xcconfig │ ├── EasyListView │ ├── EasyListView-Info.plist │ ├── EasyListView-dummy.m │ ├── EasyListView-prefix.pch │ ├── EasyListView-umbrella.h │ ├── EasyListView.debug.xcconfig │ ├── EasyListView.modulemap │ └── EasyListView.release.xcconfig │ └── Pods-EasyListViewExample │ ├── Pods-EasyListViewExample-Info.plist │ ├── Pods-EasyListViewExample-acknowledgements.markdown │ ├── Pods-EasyListViewExample-acknowledgements.plist │ ├── Pods-EasyListViewExample-dummy.m │ ├── Pods-EasyListViewExample-frameworks-Debug-input-files.xcfilelist │ ├── Pods-EasyListViewExample-frameworks-Debug-output-files.xcfilelist │ ├── Pods-EasyListViewExample-frameworks-Release-input-files.xcfilelist │ ├── Pods-EasyListViewExample-frameworks-Release-output-files.xcfilelist │ ├── Pods-EasyListViewExample-frameworks.sh │ ├── Pods-EasyListViewExample-umbrella.h │ ├── Pods-EasyListViewExample.debug.xcconfig │ ├── Pods-EasyListViewExample.modulemap │ └── Pods-EasyListViewExample.release.xcconfig ├── README.md └── Sources ├── ObjC ├── EasyListObjC.swift └── EasyListObjCDeprecated.swift └── Swift ├── EasyListAppend.swift ├── EasyListAppendDeprecated.swift ├── EasyListAttributes.swift ├── EasyListConstraint.swift ├── EasyListCoordinator.swift ├── EasyListDelete.swift ├── EasyListDeleteDeprecated.swift ├── EasyListDisposable.swift ├── EasyListExtension.swift ├── EasyListInsert.swift ├── EasyListInsertDeprecated.swift └── EasyListView.swift /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## User settings 6 | xcuserdata/ 7 | 8 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) 9 | *.xcscmblueprint 10 | *.xccheckout 11 | 12 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) 13 | build/ 14 | DerivedData/ 15 | *.moved-aside 16 | *.pbxuser 17 | !default.pbxuser 18 | *.mode1v3 19 | !default.mode1v3 20 | *.mode2v3 21 | !default.mode2v3 22 | *.perspectivev3 23 | !default.perspectivev3 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | 28 | ## App packaging 29 | *.ipa 30 | *.dSYM.zip 31 | *.dSYM 32 | 33 | ## Playgrounds 34 | timeline.xctimeline 35 | playground.xcworkspace 36 | 37 | # Swift Package Manager 38 | # 39 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 40 | # Packages/ 41 | # Package.pins 42 | # Package.resolved 43 | # *.xcodeproj 44 | # 45 | # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata 46 | # hence it is not needed unless you have added a package configuration file to your project 47 | # .swiftpm 48 | 49 | .build/ 50 | 51 | # CocoaPods 52 | # 53 | # We recommend against adding the Pods directory to your .gitignore. However 54 | # you should judge for yourself, the pros and cons are mentioned at: 55 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 56 | # 57 | # Pods/ 58 | # 59 | # Add this line if you want to avoid checking in source code from the Xcode workspace 60 | # *.xcworkspace 61 | 62 | # Carthage 63 | # 64 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 65 | # Carthage/Checkouts 66 | 67 | Carthage/Build/ 68 | 69 | # Accio dependency management 70 | Dependencies/ 71 | .accio/ 72 | 73 | # fastlane 74 | # 75 | # It is recommended to not store the screenshots in the git repo. 76 | # Instead, use fastlane to re-generate the screenshots whenever they are needed. 77 | # For more information about the recommended setup visit: 78 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 79 | 80 | fastlane/report.xml 81 | fastlane/Preview.html 82 | fastlane/screenshots/**/*.png 83 | fastlane/test_output 84 | 85 | # Code Injection 86 | # 87 | # After new code Injection tools there's a generated folder /iOSInjectionProject 88 | # https://github.com/johnno1962/injectionforxcode 89 | 90 | iOSInjectionProject/ 91 | -------------------------------------------------------------------------------- /.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /368DED3D-4B49-4487-8E45-F2BB39D190B3.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moliya/EasyListView/de589ce71c695759034df8f28d8b7b4168b4168e/368DED3D-4B49-4487-8E45-F2BB39D190B3.jpeg -------------------------------------------------------------------------------- /64DAA174-C657-4A95-B418-1DA7484EFE3F.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moliya/EasyListView/de589ce71c695759034df8f28d8b7b4168b4168e/64DAA174-C657-4A95-B418-1DA7484EFE3F.jpeg -------------------------------------------------------------------------------- /EasyListView.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | 3 | s.name = "EasyListView" 4 | s.version = "1.3.1" 5 | s.summary = "快速搭建静态及可重用列表" 6 | s.homepage = "https://github.com/moliya/EasyListView" 7 | s.license = "MIT" 8 | s.author = {'Carefree' => '946715806@qq.com'} 9 | s.source = { :git => "https://github.com/moliya/EasyListView.git", :tag => s.version} 10 | s.requires_arc = true 11 | s.platform = :ios, '9.0' 12 | s.swift_version = '5.0' 13 | s.dependency 'EasyCompatible' 14 | s.source_files = "Sources/**/*" 15 | end 16 | -------------------------------------------------------------------------------- /EasyListViewExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /EasyListViewExample.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /EasyListViewExample.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /EasyListViewExample.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /EasyListViewExample/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // EasyListViewExample 4 | // 5 | // Created by carefree on 2020/6/11. 6 | // Copyright © 2020 carefree. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 17 | // Override point for customization after application launch. 18 | if #available(iOS 13.0, *) { 19 | let appearance = UINavigationBarAppearance() 20 | appearance.configureWithOpaqueBackground() 21 | UINavigationBar.appearance().scrollEdgeAppearance = appearance 22 | UINavigationBar.appearance().standardAppearance = appearance 23 | } 24 | return true 25 | } 26 | 27 | 28 | } 29 | 30 | -------------------------------------------------------------------------------- /EasyListViewExample/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 | -------------------------------------------------------------------------------- /EasyListViewExample/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /EasyListViewExample/Assets.xcassets/avatar.imageset/054.00.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moliya/EasyListView/de589ce71c695759034df8f28d8b7b4168b4168e/EasyListViewExample/Assets.xcassets/avatar.imageset/054.00.png -------------------------------------------------------------------------------- /EasyListViewExample/Assets.xcassets/avatar.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "filename" : "054.00.png", 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /EasyListViewExample/Assets.xcassets/indicator.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "filename" : "indicator@2x.png", 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "filename" : "indicator@3x.png", 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "author" : "xcode", 20 | "version" : 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /EasyListViewExample/Assets.xcassets/indicator.imageset/indicator@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moliya/EasyListView/de589ce71c695759034df8f28d8b7b4168b4168e/EasyListViewExample/Assets.xcassets/indicator.imageset/indicator@2x.png -------------------------------------------------------------------------------- /EasyListViewExample/Assets.xcassets/indicator.imageset/indicator@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moliya/EasyListView/de589ce71c695759034df8f28d8b7b4168b4168e/EasyListViewExample/Assets.xcassets/indicator.imageset/indicator@3x.png -------------------------------------------------------------------------------- /EasyListViewExample/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 | -------------------------------------------------------------------------------- /EasyListViewExample/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /EasyListViewExample/Demo1/FormListController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FormListController.swift 3 | // EasyListViewExample 4 | // 5 | // Created by carefree on 2020/8/17. 6 | // Copyright © 2020 carefree. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import EasyListView 11 | 12 | class FormListController: UIViewController { 13 | 14 | lazy var scrollView: UIScrollView = { 15 | let scrollView = UIScrollView() 16 | scrollView.alwaysBounceVertical = true 17 | scrollView.backgroundColor = #colorLiteral(red: 0.9607843137, green: 0.9607843137, blue: 0.9607843137, alpha: 1) 18 | 19 | return scrollView 20 | }() 21 | 22 | var nameTextField: UITextField! 23 | var birthdayLabel: UILabel! 24 | 25 | override func viewDidLoad() { 26 | super.viewDidLoad() 27 | 28 | scrollView.delegate = self 29 | view.subviews(scrollView) 30 | view.layout( 31 | 0, 32 | |scrollView|, 33 | 0 34 | ) 35 | 36 | let addSeparator: () -> Void = { 37 | let view = UIView() 38 | view.backgroundColor = #colorLiteral(red: 0.8979505897, green: 0.8981012702, blue: 0.8979307413, alpha: 1) 39 | view.height(0.5) 40 | 41 | self.scrollView.easy.appendView(view) 42 | } 43 | 44 | scrollView.easy.appendView { 45 | let label = UILabel() 46 | label.numberOfLines = 0 47 | label.font = .systemFont(ofSize: 14) 48 | label.textColor = .lightGray 49 | label.text = "Section Title" 50 | return label 51 | }.insets(UIEdgeInsets(top: 10, left: 16, bottom: 0, right: 16)) 52 | 53 | let nameCell: UIView = { 54 | let cell = EasyListCell() 55 | cell.textLabel.text = "Name" 56 | cell.showIndicator = false 57 | 58 | let textField = UITextField() 59 | textField.textAlignment = .right 60 | textField.placeholder = "type your name" 61 | self.nameTextField = textField 62 | 63 | cell.subviews(textField) 64 | cell.height(48) 65 | cell.layout( 66 | 0, 67 | textField-16-|, 68 | 0 69 | ) 70 | textField.Leading == cell.textLabel.Trailing + 10 71 | 72 | return cell 73 | }() 74 | scrollView.easy.appendView(nameCell).spacing(10) 75 | 76 | addSeparator() 77 | 78 | let emailCell: UIView = { 79 | let cell = EasyListCell() 80 | cell.textLabel.text = "Email" 81 | cell.showIndicator = false 82 | 83 | let textField = UITextField() 84 | textField.textAlignment = .right 85 | textField.placeholder = "email@domain.com" 86 | 87 | cell.subviews(textField) 88 | cell.height(48) 89 | cell.layout( 90 | 0, 91 | textField-16-|, 92 | 0 93 | ) 94 | textField.Leading == cell.textLabel.Trailing + 10 95 | 96 | return cell 97 | }() 98 | scrollView.easy.appendView(emailCell) 99 | 100 | addSeparator() 101 | 102 | let birthdayCell: UIView = { 103 | let cell = EasyListCell() 104 | cell.textLabel.text = "Birthday" 105 | cell.onTap = {[unowned self] in 106 | self.tapBirthdayCell() 107 | } 108 | 109 | let label = UILabel() 110 | label.textAlignment = .right 111 | label.textColor = #colorLiteral(red: 0.4, green: 0.4, blue: 0.4, alpha: 1) 112 | let formatter = DateFormatter() 113 | formatter.dateFormat = "yyyy-MM-dd" 114 | label.text = formatter.string(from: Date()) 115 | self.birthdayLabel = label 116 | 117 | cell.subviews(label) 118 | cell.height(48) 119 | cell.layout( 120 | label.centerVertically()-30-| 121 | ) 122 | label.Leading == cell.textLabel.Trailing + 10 123 | 124 | return cell 125 | }() 126 | scrollView.easy.appendView(birthdayCell).identifier("birthday") 127 | 128 | addSeparator() 129 | 130 | let switchCell: UIView = { 131 | let cell = EasyListCell() 132 | cell.textLabel.text = "Other" 133 | cell.showIndicator = false 134 | 135 | let swt = UISwitch() 136 | 137 | cell.subviews(swt) 138 | cell.height(48) 139 | cell.layout( 140 | swt.centerVertically()-16-| 141 | ) 142 | 143 | return cell 144 | }() 145 | scrollView.easy.appendView(switchCell) 146 | 147 | scrollView.easy.appendView { 148 | let label = UILabel() 149 | label.numberOfLines = 0 150 | label.font = .systemFont(ofSize: 16) 151 | label.textColor = .lightGray 152 | label.text = "1.AAA\n2.BBB\n3.CCC" 153 | return label 154 | }.insets(UIEdgeInsets(top: 10, left: 16, bottom: 0, right: 16)) 155 | 156 | let inputView: UIView = { 157 | let view = UIView() 158 | view.backgroundColor = .white 159 | 160 | let label = UILabel() 161 | label.font = .systemFont(ofSize: 12) 162 | label.textColor = #colorLiteral(red: 0.2, green: 0.2, blue: 0.2, alpha: 1) 163 | label.text = "Description" 164 | 165 | let textView = UITextView() 166 | textView.layer.cornerRadius = 6 167 | textView.layer.borderColor = #colorLiteral(red: 0.8039215803, green: 0.8039215803, blue: 0.8039215803, alpha: 1) 168 | textView.layer.borderWidth = 1 / UIScreen.main.scale 169 | 170 | view.subviews(label, textView) 171 | view.height(150) 172 | view.layout( 173 | 15, 174 | |-16-label, 175 | 10, 176 | |-15-textView-15-|, 177 | 10 178 | ) 179 | 180 | return view 181 | }() 182 | scrollView.easy.appendView(inputView).spacing(10) 183 | 184 | let button = UIButton(type: .custom) 185 | button.setTitle("Done", for: .normal) 186 | button.backgroundColor = #colorLiteral(red: 0.1764705926, green: 0.4980392158, blue: 0.7568627596, alpha: 1) 187 | button.layer.cornerRadius = 6 188 | button.clipsToBounds = true 189 | button.height(45) 190 | button.addTarget(self, action: #selector(tapDone), for: .touchUpInside) 191 | scrollView.easy.appendView(button).insets(UIEdgeInsets(top: 40, left: 16, bottom: 20, right: 16)) 192 | } 193 | 194 | deinit { 195 | print("deinit") 196 | } 197 | 198 | func tapBirthdayCell() { 199 | if let _ = scrollView.easy.getElement(identifier: "birthdaySelector") as? UIDatePicker { 200 | scrollView.easy.deleteView("birthdaySelector") 201 | birthdayLabel.textColor = #colorLiteral(red: 0.4, green: 0.4, blue: 0.4, alpha: 1) 202 | return 203 | } 204 | 205 | let picker = UIDatePicker() 206 | picker.backgroundColor = .white 207 | picker.datePickerMode = .date 208 | if #available(iOS 13.4, *) { 209 | picker.preferredDatePickerStyle = .wheels 210 | } 211 | picker.addTarget(self, action: #selector(datePickerChanged(_:)), for: .valueChanged) 212 | 213 | if let text = birthdayLabel.text, !text.isEmpty { 214 | let formatter = DateFormatter() 215 | formatter.dateFormat = "yyyy-MM-dd" 216 | picker.date = formatter.date(from: text) ?? Date() 217 | } 218 | birthdayLabel.textColor = #colorLiteral(red: 0.9098039269, green: 0.4784313738, blue: 0.6431372762, alpha: 1) 219 | 220 | scrollView.easy.beginUpdates() 221 | scrollView.easy.insertView(picker, after: "birthday").identifier("birthdaySelector") 222 | scrollView.easy.endUpdates() 223 | } 224 | 225 | @objc func datePickerChanged(_ sender: UIDatePicker) { 226 | let formatter = DateFormatter() 227 | formatter.dateFormat = "yyyy-MM-dd" 228 | birthdayLabel.text = formatter.string(from: sender.date) 229 | } 230 | 231 | @objc func tapDone() { 232 | view.endEditing(true) 233 | let alert = UIAlertController(title: "Hello!\(nameTextField.text ?? "")", message: nil, preferredStyle: .alert) 234 | alert.addAction(UIAlertAction(title: "OK", style: .cancel, handler: nil)) 235 | self.present(alert, animated: true, completion: nil) 236 | } 237 | } 238 | 239 | extension FormListController: UIScrollViewDelegate { 240 | func scrollViewDidScroll(_ scrollView: UIScrollView) { 241 | view.endEditing(true) 242 | } 243 | } 244 | -------------------------------------------------------------------------------- /EasyListViewExample/Demo2/UserCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UserCell.swift 3 | // EasyListViewExample 4 | // 5 | // Created by carefree on 2020/8/17. 6 | // Copyright © 2020 carefree. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class UserCell: UIView { 12 | 13 | var avatarView: UIImageView! 14 | var nameLabel: UILabel! 15 | 16 | required init?(coder: NSCoder) { 17 | super.init(coder: coder) 18 | commonInit() 19 | } 20 | 21 | override init(frame: CGRect) { 22 | super.init(frame: frame) 23 | commonInit() 24 | } 25 | 26 | func commonInit() { 27 | backgroundColor = .white 28 | 29 | avatarView = UIImageView() 30 | avatarView.contentMode = .scaleAspectFit 31 | nameLabel = UILabel() 32 | 33 | let separator = UIView() 34 | separator.backgroundColor = #colorLiteral(red: 0.8979505897, green: 0.8981012702, blue: 0.8979307413, alpha: 1) 35 | 36 | subviews(avatarView, nameLabel, separator) 37 | height(66) 38 | layout( 39 | 5, 40 | |-16-avatarView, 41 | 5, 42 | |-16-separator.height(1 / UIScreen.main.scale)|, 43 | 0 44 | ) 45 | avatarView.Width == avatarView.Height 46 | nameLabel.Top == avatarView.Top 47 | nameLabel.Leading == avatarView.Trailing + 5 48 | } 49 | 50 | func set(name: String?, avatar: UIImage?) { 51 | avatarView.image = avatar 52 | nameLabel.text = name 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /EasyListViewExample/Demo2/UserListController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UserListController.swift 3 | // EasyListViewExample 4 | // 5 | // Created by carefree on 2020/8/17. 6 | // Copyright © 2020 carefree. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import EasyListView 11 | 12 | class UserListController: UIViewController { 13 | 14 | lazy var scrollView: UIScrollView = { 15 | let scrollView = UIScrollView() 16 | scrollView.alwaysBounceVertical = true 17 | scrollView.backgroundColor = #colorLiteral(red: 0.9607843137, green: 0.9607843137, blue: 0.9607843137, alpha: 1) 18 | 19 | return scrollView 20 | }() 21 | 22 | var users = [String]() 23 | 24 | override func viewDidLoad() { 25 | super.viewDidLoad() 26 | 27 | scrollView.delegate = self 28 | view.subviews(scrollView) 29 | view.layout( 30 | 0, 31 | |scrollView|, 32 | 0 33 | ) 34 | 35 | scrollView.es.addPullToRefresh {[unowned self] in 36 | self.loadData() 37 | } 38 | scrollView.es.addInfiniteScrolling {[unowned self] in 39 | self.loadMoreData() 40 | } 41 | 42 | let activity = UIActivityIndicatorView(style: .gray) 43 | scrollView.subviews(activity) 44 | activity.centerInContainer() 45 | activity.startAnimating() 46 | loadData() 47 | } 48 | 49 | deinit { 50 | print("deinit") 51 | } 52 | 53 | @objc func loadData() { 54 | DispatchQueue.main.asyncAfter(deadline: .now() + 1) { 55 | if self.users.count == 0 { 56 | self.scrollView.subviews.first { $0 is UIActivityIndicatorView }?.removeFromSuperview() 57 | } 58 | 59 | //删除旧的数据 60 | self.users.removeAll() 61 | self.scrollView.easy.deleteAll() 62 | 63 | //添加新数据 64 | for i in 1 ..< 11 { 65 | self.users.append("User No.\(i)") 66 | } 67 | 68 | for index in 0 ..< self.users.count { 69 | let view = self.scrollView.easy.disposableView {[unowned self] in 70 | let cell = UserCell() 71 | cell.set(name: self.users[index], avatar: UIImage(named: "avatar")) 72 | 73 | return cell 74 | } 75 | self.scrollView.easy.appendView(view) 76 | } 77 | 78 | if self.scrollView.header?.isRefreshing == true { 79 | self.scrollView.header?.stopRefreshing() 80 | } 81 | } 82 | } 83 | 84 | @objc func loadMoreData() { 85 | DispatchQueue.main.asyncAfter(deadline: .now() + 1) { 86 | let offset = self.users.count 87 | //添加新数据 88 | for i in 1 ..< 11 { 89 | self.users.append("User No.\(i + offset)") 90 | } 91 | 92 | self.scrollView.easy.beginUpdates() 93 | 94 | for index in offset ..< self.users.count { 95 | let view = self.scrollView.easy.disposableView {[unowned self] in 96 | let cell = UserCell() 97 | cell.set(name: self.users[index], avatar: UIImage(named: "avatar")) 98 | return cell 99 | } 100 | self.scrollView.easy.appendView(view) 101 | } 102 | 103 | self.scrollView.easy.endUpdates() 104 | 105 | self.scrollView.footer?.stopRefreshing() 106 | } 107 | } 108 | 109 | 110 | } 111 | 112 | extension UserListController: UIScrollViewDelegate { 113 | func scrollViewDidScroll(_ scrollView: UIScrollView) { 114 | scrollView.easy.triggerDisposable() 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /EasyListViewExample/EasyListViewExample-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 "ObjCTestController.h" 6 | -------------------------------------------------------------------------------- /EasyListViewExample/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 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /EasyListViewExample/Main/EasyListCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EasyListCell.swift 3 | // EasyListViewExample 4 | // 5 | // Created by carefree on 2020/8/17. 6 | // Copyright © 2020 carefree. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class EasyListCell: UIView { 12 | 13 | public var onTap: (() -> Void)? 14 | public let textLabel = UILabel() 15 | public var showIndicator = true { 16 | didSet { 17 | indicatorView.isHidden = !showIndicator 18 | } 19 | } 20 | 21 | private var normalColor: UIColor? 22 | private var highlightedColor = UIColor(red: 218/255.0, green: 218/255.0, blue: 218/255.0, alpha: 1) 23 | private var indicatorView = UIImageView() 24 | 25 | override init(frame: CGRect) { 26 | super.init(frame: frame) 27 | commonInit() 28 | } 29 | 30 | required init?(coder: NSCoder) { 31 | super.init(coder: coder) 32 | commonInit() 33 | } 34 | 35 | func commonInit() { 36 | backgroundColor = .white 37 | addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(tapAction))) 38 | 39 | textLabel.setContentHuggingPriority(.required, for: .horizontal) 40 | textLabel.font = .systemFont(ofSize: 16) 41 | textLabel.textColor = .darkText 42 | 43 | indicatorView.image = UIImage(named: "indicator") 44 | 45 | subviews(textLabel, indicatorView) 46 | layout( 47 | |-16-textLabel-""-indicatorView-16-| 48 | ) 49 | textLabel.centerVertically() 50 | } 51 | 52 | @objc func tapAction() { 53 | onTap?() 54 | } 55 | 56 | override func touchesBegan(_ touches: Set, with event: UIEvent?) { 57 | super.touchesBegan(touches, with: event) 58 | 59 | normalColor = backgroundColor 60 | backgroundColor = highlightedColor 61 | } 62 | 63 | override func touchesEnded(_ touches: Set, with event: UIEvent?) { 64 | super.touchesEnded(touches, with: event) 65 | 66 | backgroundColor = normalColor 67 | } 68 | 69 | override func touchesCancelled(_ touches: Set, with event: UIEvent?) { 70 | super.touchesCancelled(touches, with: event) 71 | 72 | backgroundColor = normalColor 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /EasyListViewExample/Main/MainViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MainViewController.swift 3 | // EasyListViewExample 4 | // 5 | // Created by carefree on 2020/8/17. 6 | // Copyright © 2020 carefree. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import EasyListView 11 | 12 | class MainViewController: UIViewController { 13 | 14 | @IBOutlet weak var scrollView: UIScrollView! 15 | 16 | override func viewDidLoad() { 17 | super.viewDidLoad() 18 | 19 | let cell1: UIView = { 20 | let cell = EasyListCell() 21 | cell.textLabel.text = "示例-表单" 22 | cell.height(44) 23 | cell.onTap = { [unowned self] in 24 | self.navigationController?.pushViewController(FormListController(), animated: true) 25 | } 26 | 27 | return cell 28 | }() 29 | scrollView.easy.appendView(cell1).spacing(10) 30 | 31 | let cell2: UIView = { 32 | let cell = EasyListCell() 33 | cell.textLabel.text = "示例-列表" 34 | cell.height(44) 35 | cell.onTap = { [unowned self] in 36 | self.navigationController?.pushViewController(UserListController(), animated: true) 37 | } 38 | 39 | return cell 40 | }() 41 | scrollView.easy.appendView(cell2).spacing(0.5) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /EasyListViewExample/Main/ObjCTestController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ObjCTestController.h 3 | // EasyListViewExample 4 | // 5 | // Created by carefree on 2020/8/18. 6 | // Copyright © 2020 carefree. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface ObjCTestController : UIViewController 14 | 15 | @end 16 | 17 | NS_ASSUME_NONNULL_END 18 | -------------------------------------------------------------------------------- /EasyListViewExample/Main/ObjCTestController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ObjCTestController.m 3 | // EasyListViewExample 4 | // 5 | // Created by carefree on 2020/8/18. 6 | // Copyright © 2020 carefree. All rights reserved. 7 | // 8 | 9 | #import "ObjCTestController.h" 10 | #import 11 | 12 | @interface ObjCTestController () 13 | 14 | @end 15 | 16 | @implementation ObjCTestController 17 | 18 | - (void)viewDidLoad { 19 | [super viewDidLoad]; 20 | 21 | EasyListView *listView = [[EasyListView alloc] init]; 22 | [listView easy_beginUpdates]; 23 | [listView easy_endUpdatesWithCompletion:nil]; 24 | 25 | } 26 | 27 | @end 28 | -------------------------------------------------------------------------------- /EasyListViewExample/Vendor/ESPullToRefresh/Animator/Base.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | "Pull to refresh" = "Pull to refresh"; 2 | "Release to refresh" = "Release to refresh"; 3 | "Loading..." = "Loading..."; 4 | "Loading more" = "Loading more"; 5 | "No more data" = "No more data"; 6 | -------------------------------------------------------------------------------- /EasyListViewExample/Vendor/ESPullToRefresh/Animator/ESRefreshFooterAnimator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ESRefreshFooterAnimator.swift 3 | // 4 | // Created by egg swift on 16/4/7. 5 | // Copyright (c) 2013-2016 ESPullToRefresh (https://github.com/eggswift/pull-to-refresh) 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | // THE SOFTWARE. 24 | // 25 | 26 | import UIKit 27 | 28 | open class ESRefreshFooterAnimator: UIView, ESRefreshProtocol, ESRefreshAnimatorProtocol { 29 | 30 | open var loadingMoreDescription: String = NSLocalizedString("Loading more", comment: "") 31 | open var noMoreDataDescription: String = NSLocalizedString("No more data", comment: "") 32 | open var loadingDescription: String = NSLocalizedString("Loading...", comment: "") 33 | 34 | open var view: UIView { return self } 35 | open var duration: TimeInterval = 0.3 36 | open var insets: UIEdgeInsets = UIEdgeInsets.zero 37 | open var trigger: CGFloat = 42.0 38 | open var executeIncremental: CGFloat = 42.0 39 | open var state: ESRefreshViewState = .pullToRefresh 40 | 41 | fileprivate let titleLabel: UILabel = { 42 | let label = UILabel.init(frame: CGRect.zero) 43 | label.font = UIFont.systemFont(ofSize: 14.0) 44 | label.textColor = UIColor.init(white: 160.0 / 255.0, alpha: 1.0) 45 | label.textAlignment = .center 46 | return label 47 | }() 48 | 49 | fileprivate let indicatorView: UIActivityIndicatorView = { 50 | let indicatorView = UIActivityIndicatorView.init(style: .gray) 51 | indicatorView.isHidden = true 52 | return indicatorView 53 | }() 54 | 55 | public override init(frame: CGRect) { 56 | super.init(frame: frame) 57 | titleLabel.text = loadingMoreDescription 58 | addSubview(titleLabel) 59 | addSubview(indicatorView) 60 | } 61 | 62 | public required init(coder aDecoder: NSCoder) { 63 | fatalError("init(coder:) has not been implemented") 64 | } 65 | 66 | open func refreshAnimationBegin(view: ESRefreshComponent) { 67 | indicatorView.startAnimating() 68 | titleLabel.text = loadingDescription 69 | indicatorView.isHidden = false 70 | } 71 | 72 | open func refreshAnimationEnd(view: ESRefreshComponent) { 73 | indicatorView.stopAnimating() 74 | titleLabel.text = loadingMoreDescription 75 | indicatorView.isHidden = true 76 | } 77 | 78 | open func refresh(view: ESRefreshComponent, progressDidChange progress: CGFloat) { 79 | // do nothing 80 | } 81 | 82 | open func refresh(view: ESRefreshComponent, stateDidChange state: ESRefreshViewState) { 83 | guard self.state != state else { 84 | return 85 | } 86 | self.state = state 87 | 88 | switch state { 89 | case .refreshing, .autoRefreshing : 90 | titleLabel.text = loadingDescription 91 | break 92 | case .noMoreData: 93 | titleLabel.text = noMoreDataDescription 94 | break 95 | case .pullToRefresh: 96 | titleLabel.text = loadingMoreDescription 97 | break 98 | default: 99 | break 100 | } 101 | self.setNeedsLayout() 102 | } 103 | 104 | open override func layoutSubviews() { 105 | super.layoutSubviews() 106 | let s = self.bounds.size 107 | let w = s.width 108 | let h = s.height 109 | 110 | titleLabel.sizeToFit() 111 | titleLabel.center = CGPoint.init(x: w / 2.0, y: h / 2.0 - 5.0) 112 | indicatorView.center = CGPoint.init(x: titleLabel.frame.origin.x - 18.0, y: titleLabel.center.y) 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /EasyListViewExample/Vendor/ESPullToRefresh/Animator/ESRefreshHeaderAnimator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ESRefreshHeaderView.swift 3 | // 4 | // Created by egg swift on 16/4/7. 5 | // Copyright (c) 2013-2016 ESPullToRefresh (https://github.com/eggswift/pull-to-refresh) 6 | // Icon from http://www.iconfont.cn 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | // 26 | 27 | import Foundation 28 | import QuartzCore 29 | import UIKit 30 | 31 | open class ESRefreshHeaderAnimator: UIView, ESRefreshProtocol, ESRefreshAnimatorProtocol, ESRefreshImpactProtocol { 32 | open var pullToRefreshDescription = NSLocalizedString("Pull to refresh", comment: "") { 33 | didSet { 34 | if pullToRefreshDescription != oldValue { 35 | titleLabel.text = pullToRefreshDescription 36 | } 37 | } 38 | } 39 | open var releaseToRefreshDescription = NSLocalizedString("Release to refresh", comment: "") 40 | open var loadingDescription = NSLocalizedString("Loading...", comment: "") 41 | 42 | open var view: UIView { return self } 43 | open var insets: UIEdgeInsets = UIEdgeInsets.zero 44 | open var trigger: CGFloat = 60.0 45 | open var executeIncremental: CGFloat = 60.0 46 | open var state: ESRefreshViewState = .pullToRefresh 47 | 48 | fileprivate let imageView: UIImageView = { 49 | let imageView = UIImageView.init() 50 | let frameworkBundle = Bundle(for: ESRefreshAnimator.self) 51 | if /* CocoaPods static */ let path = frameworkBundle.path(forResource: "ESPullToRefresh", ofType: "bundle"),let bundle = Bundle(path: path) { 52 | imageView.image = UIImage(named: "icon_pull_to_refresh_arrow", in: bundle, compatibleWith: nil) 53 | }else if /* Carthage */ let bundle = Bundle.init(identifier: "com.eggswift.ESPullToRefresh") { 54 | imageView.image = UIImage(named: "icon_pull_to_refresh_arrow", in: bundle, compatibleWith: nil) 55 | } else if /* CocoaPods */ let bundle = Bundle.init(identifier: "org.cocoapods.ESPullToRefresh") { 56 | imageView.image = UIImage(named: "ESPullToRefresh.bundle/icon_pull_to_refresh_arrow", in: bundle, compatibleWith: nil) 57 | } else /* Manual */ { 58 | imageView.image = UIImage(named: "icon_pull_to_refresh_arrow") 59 | } 60 | return imageView 61 | }() 62 | 63 | fileprivate let titleLabel: UILabel = { 64 | let label = UILabel.init(frame: CGRect.zero) 65 | label.font = UIFont.systemFont(ofSize: 14.0) 66 | label.textColor = UIColor.init(white: 0.625, alpha: 1.0) 67 | label.textAlignment = .left 68 | return label 69 | }() 70 | 71 | fileprivate let indicatorView: UIActivityIndicatorView = { 72 | let indicatorView = UIActivityIndicatorView.init(style: .gray) 73 | indicatorView.isHidden = true 74 | return indicatorView 75 | }() 76 | 77 | public override init(frame: CGRect) { 78 | super.init(frame: frame) 79 | titleLabel.text = pullToRefreshDescription 80 | self.addSubview(imageView) 81 | self.addSubview(titleLabel) 82 | self.addSubview(indicatorView) 83 | } 84 | 85 | public required init(coder aDecoder: NSCoder) { 86 | fatalError("init(coder:) has not been implemented") 87 | } 88 | 89 | open func refreshAnimationBegin(view: ESRefreshComponent) { 90 | indicatorView.startAnimating() 91 | indicatorView.isHidden = false 92 | imageView.isHidden = true 93 | titleLabel.text = loadingDescription 94 | imageView.transform = CGAffineTransform(rotationAngle: 0.000001 - CGFloat.pi) 95 | } 96 | 97 | open func refreshAnimationEnd(view: ESRefreshComponent) { 98 | indicatorView.stopAnimating() 99 | indicatorView.isHidden = true 100 | imageView.isHidden = false 101 | titleLabel.text = pullToRefreshDescription 102 | imageView.transform = CGAffineTransform.identity 103 | } 104 | 105 | open func refresh(view: ESRefreshComponent, progressDidChange progress: CGFloat) { 106 | // Do nothing 107 | 108 | } 109 | 110 | open func refresh(view: ESRefreshComponent, stateDidChange state: ESRefreshViewState) { 111 | guard self.state != state else { 112 | return 113 | } 114 | self.state = state 115 | 116 | switch state { 117 | case .refreshing, .autoRefreshing: 118 | titleLabel.text = loadingDescription 119 | self.setNeedsLayout() 120 | break 121 | case .releaseToRefresh: 122 | titleLabel.text = releaseToRefreshDescription 123 | self.setNeedsLayout() 124 | self.impact() 125 | UIView.animate(withDuration: 0.2, delay: 0.0, options: UIView.AnimationOptions(), animations: { 126 | [weak self] in 127 | self?.imageView.transform = CGAffineTransform(rotationAngle: 0.000001 - CGFloat.pi) 128 | }) { (animated) in } 129 | break 130 | case .pullToRefresh: 131 | titleLabel.text = pullToRefreshDescription 132 | self.setNeedsLayout() 133 | UIView.animate(withDuration: 0.2, delay: 0.0, options: UIView.AnimationOptions(), animations: { 134 | [weak self] in 135 | self?.imageView.transform = CGAffineTransform.identity 136 | }) { (animated) in } 137 | break 138 | default: 139 | break 140 | } 141 | } 142 | 143 | open override func layoutSubviews() { 144 | super.layoutSubviews() 145 | let s = self.bounds.size 146 | let w = s.width 147 | let h = s.height 148 | 149 | UIView.performWithoutAnimation { 150 | titleLabel.sizeToFit() 151 | titleLabel.center = CGPoint.init(x: w / 2.0, y: h / 2.0) 152 | indicatorView.center = CGPoint.init(x: titleLabel.frame.origin.x - 16.0, y: h / 2.0) 153 | imageView.frame = CGRect.init(x: titleLabel.frame.origin.x - 28.0, y: (h - 18.0) / 2.0, width: 18.0, height: 18.0) 154 | } 155 | } 156 | 157 | } 158 | -------------------------------------------------------------------------------- /EasyListViewExample/Vendor/ESPullToRefresh/Animator/icon_pull_to_refresh_arrow@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moliya/EasyListView/de589ce71c695759034df8f28d8b7b4168b4168e/EasyListViewExample/Vendor/ESPullToRefresh/Animator/icon_pull_to_refresh_arrow@2x.png -------------------------------------------------------------------------------- /EasyListViewExample/Vendor/ESPullToRefresh/Animator/zh-Hans.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | "Pull to refresh" = "下拉刷新"; 2 | "Release to refresh" = "松开刷新"; 3 | "Loading..." = "加载中"; 4 | "Loading more" = "正在加载更多"; 5 | "No more data" = "没有更多的了"; 6 | -------------------------------------------------------------------------------- /EasyListViewExample/Vendor/ESPullToRefresh/Animator/zh-Hant.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | "Pull to refresh" = "下拉刷新"; 2 | "Release to refresh" = "松開刷新"; 3 | "Loading..." = "載入中"; 4 | "Loading more" = "正在載入更多"; 5 | "No more data" = "沒有更多的了"; 6 | -------------------------------------------------------------------------------- /EasyListViewExample/Vendor/ESPullToRefresh/ES.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ES.swift 3 | // ESPullToRefreshExample 4 | // 5 | // Created by 木瓜 on 2017/4/25. 6 | // Copyright © 2017年 egg swift. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | 12 | public protocol ESExtensionsProvider: class { 13 | associatedtype CompatibleType 14 | var es: CompatibleType { get } 15 | } 16 | 17 | extension ESExtensionsProvider { 18 | /// A proxy which hosts reactive extensions for `self`. 19 | public var es: ES { 20 | return ES(self) 21 | } 22 | 23 | } 24 | 25 | public struct ES { 26 | public let base: Base 27 | 28 | // Construct a proxy. 29 | // 30 | // - parameters: 31 | // - base: The object to be proxied. 32 | fileprivate init(_ base: Base) { 33 | self.base = base 34 | } 35 | } 36 | 37 | // 38 | extension UIScrollView: ESExtensionsProvider {} 39 | 40 | 41 | -------------------------------------------------------------------------------- /EasyListViewExample/Vendor/ESPullToRefresh/ESPullToRefresh+Manager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ESPullToRefresh+Manager.swift 3 | // 4 | // Created by egg swift on 16/4/7. 5 | // Copyright (c) 2013-2016 ESPullToRefresh (https://github.com/eggswift/pull-to-refresh) 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | // THE SOFTWARE. 24 | // 25 | 26 | import Foundation 27 | 28 | open class ESRefreshDataManager { 29 | 30 | static let sharedManager = ESRefreshDataManager.init() 31 | 32 | static let lastRefreshKey: String = "com.espulltorefresh.lastRefreshKey" 33 | static let expiredTimeIntervalKey: String = "com.espulltorefresh.expiredTimeIntervalKey" 34 | open var lastRefreshInfo = [String: Date]() 35 | open var expiredTimeIntervalInfo = [String: TimeInterval]() 36 | 37 | public required init() { 38 | if let lastRefreshInfo = UserDefaults.standard.dictionary(forKey: ESRefreshDataManager.lastRefreshKey) as? [String: Date] { 39 | self.lastRefreshInfo = lastRefreshInfo 40 | } 41 | if let expiredTimeIntervalInfo = UserDefaults.standard.dictionary(forKey: ESRefreshDataManager.expiredTimeIntervalKey) as? [String: TimeInterval] { 42 | self.expiredTimeIntervalInfo = expiredTimeIntervalInfo 43 | } 44 | } 45 | 46 | open func date(forKey key: String) -> Date? { 47 | let date = lastRefreshInfo[key] 48 | return date 49 | } 50 | 51 | open func setDate(_ date: Date?, forKey key: String) { 52 | lastRefreshInfo[key] = date 53 | UserDefaults.standard.set(lastRefreshInfo, forKey: ESRefreshDataManager.lastRefreshKey) 54 | UserDefaults.standard.synchronize() 55 | } 56 | 57 | open func expiredTimeInterval(forKey key: String) -> TimeInterval? { 58 | let interval = expiredTimeIntervalInfo[key] 59 | return interval 60 | } 61 | 62 | open func setExpiredTimeInterval(_ interval: TimeInterval?, forKey key: String) { 63 | expiredTimeIntervalInfo[key] = interval 64 | UserDefaults.standard.set(expiredTimeIntervalInfo, forKey: ESRefreshDataManager.expiredTimeIntervalKey) 65 | UserDefaults.standard.synchronize() 66 | } 67 | 68 | open func isExpired(forKey key: String) -> Bool { 69 | guard let date = date(forKey: key) else { 70 | return true 71 | } 72 | guard let interval = expiredTimeInterval(forKey: key) else { 73 | return false 74 | } 75 | if date.timeIntervalSinceNow < -interval { 76 | return true // Expired 77 | } 78 | return false 79 | } 80 | 81 | open func isExpired(forKey key: String, block: ((Bool) -> ())?) { 82 | DispatchQueue.global().async { 83 | [weak self] in 84 | let result = self?.isExpired(forKey: key) ?? true 85 | DispatchQueue.main.async(execute: { 86 | block?(result) 87 | }) 88 | } 89 | } 90 | 91 | public static func clearAll() { 92 | self.clearLastRefreshInfo() 93 | self.clearExpiredTimeIntervalInfo() 94 | } 95 | 96 | public static func clearLastRefreshInfo() { 97 | UserDefaults.standard.set(nil, forKey: ESRefreshDataManager.lastRefreshKey) 98 | } 99 | 100 | public static func clearExpiredTimeIntervalInfo() { 101 | UserDefaults.standard.set(nil, forKey: ESRefreshDataManager.expiredTimeIntervalKey) 102 | } 103 | 104 | } 105 | -------------------------------------------------------------------------------- /EasyListViewExample/Vendor/ESPullToRefresh/ESRefreshAnimator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ESRefreshAnimator.swift 3 | // 4 | // Created by egg swift on 16/4/7. 5 | // Copyright (c) 2013-2016 ESPullToRefresh (https://github.com/eggswift/pull-to-refresh) 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | // THE SOFTWARE. 24 | // 25 | 26 | import Foundation 27 | import UIKit 28 | 29 | open class ESRefreshAnimator: ESRefreshProtocol, ESRefreshAnimatorProtocol { 30 | // The view that called when component refresh, returns a custom view or self if 'self' is the customized views. 31 | open var view: UIView 32 | // Customized inset. 33 | open var insets: UIEdgeInsets 34 | // Refresh event is executed threshold required y offset, set a value greater than 0.0, the default is 60.0 35 | open var trigger: CGFloat = 60.0 36 | // Offset y refresh event executed by this parameter you can customize the animation to perform when you refresh the view of reservations height 37 | open var executeIncremental: CGFloat = 60.0 38 | // Current refresh state, default is .pullToRefresh 39 | open var state: ESRefreshViewState = .pullToRefresh 40 | 41 | public init() { 42 | view = UIView() 43 | insets = UIEdgeInsets.zero 44 | } 45 | 46 | open func refreshAnimationBegin(view: ESRefreshComponent) { 47 | /// Do nothing! 48 | } 49 | 50 | open func refreshAnimationWillEnd(view: ESRefreshComponent) { 51 | /// Do nothing! 52 | } 53 | 54 | open func refreshAnimationEnd(view: ESRefreshComponent) { 55 | /// Do nothing! 56 | } 57 | 58 | open func refresh(view: ESRefreshComponent, progressDidChange progress: CGFloat) { 59 | /// Do nothing! 60 | } 61 | 62 | open func refresh(view: ESRefreshComponent, stateDidChange state: ESRefreshViewState) { 63 | /// Do nothing! 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /EasyListViewExample/Vendor/ESPullToRefresh/ESRefreshComponent.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ESRefreshComponent.swift 3 | // 4 | // Created by egg swift on 16/4/7. 5 | // Copyright (c) 2013-2016 ESPullToRefresh (https://github.com/eggswift/pull-to-refresh) 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | // THE SOFTWARE. 24 | // 25 | 26 | import Foundation 27 | import UIKit 28 | 29 | public typealias ESRefreshHandler = (() -> ()) 30 | 31 | open class ESRefreshComponent: UIView { 32 | 33 | open weak var scrollView: UIScrollView? 34 | 35 | /// @param handler Refresh callback method 36 | open var handler: ESRefreshHandler? 37 | 38 | /// @param animator Animated view refresh controls, custom must comply with the following two protocol 39 | open var animator: (ESRefreshProtocol & ESRefreshAnimatorProtocol)! 40 | 41 | /// @param refreshing or not 42 | fileprivate var _isRefreshing = false 43 | open var isRefreshing: Bool { 44 | get { 45 | return self._isRefreshing 46 | } 47 | } 48 | 49 | /// @param auto refreshing or not 50 | fileprivate var _isAutoRefreshing = false 51 | open var isAutoRefreshing: Bool { 52 | get { 53 | return self._isAutoRefreshing 54 | } 55 | } 56 | 57 | /// @param tag observing 58 | fileprivate var isObservingScrollView = false 59 | fileprivate var isIgnoreObserving = false 60 | 61 | public override init(frame: CGRect) { 62 | super.init(frame: frame) 63 | autoresizingMask = [.flexibleLeftMargin, .flexibleWidth, .flexibleRightMargin] 64 | } 65 | 66 | public convenience init(frame: CGRect, handler: @escaping ESRefreshHandler) { 67 | self.init(frame: frame) 68 | self.handler = handler 69 | self.animator = ESRefreshAnimator.init() 70 | } 71 | 72 | public convenience init(frame: CGRect, handler: @escaping ESRefreshHandler, animator: ESRefreshProtocol & ESRefreshAnimatorProtocol) { 73 | self.init(frame: frame) 74 | self.handler = handler 75 | self.animator = animator 76 | } 77 | 78 | public required init?(coder aDecoder: NSCoder) { 79 | fatalError("init(coder:) has not been implemented") 80 | } 81 | 82 | deinit { 83 | removeObserver() 84 | } 85 | 86 | open override func willMove(toSuperview newSuperview: UIView?) { 87 | super.willMove(toSuperview: newSuperview) 88 | /// Remove observer from superview immediately 89 | self.removeObserver() 90 | DispatchQueue.main.async { [weak self, newSuperview] in 91 | /// Add observer to new superview in next runloop 92 | self?.addObserver(newSuperview) 93 | } 94 | } 95 | 96 | open override func didMoveToSuperview() { 97 | super.didMoveToSuperview() 98 | self.scrollView = self.superview as? UIScrollView 99 | if let _ = animator { 100 | let v = animator.view 101 | if v.superview == nil { 102 | let inset = animator.insets 103 | self.addSubview(v) 104 | v.frame = CGRect.init(x: inset.left, 105 | y: inset.right, 106 | width: self.bounds.size.width - inset.left - inset.right, 107 | height: self.bounds.size.height - inset.top - inset.bottom) 108 | v.autoresizingMask = [ 109 | .flexibleWidth, 110 | .flexibleTopMargin, 111 | .flexibleHeight, 112 | .flexibleBottomMargin 113 | ] 114 | } 115 | } 116 | } 117 | 118 | // MARK: - Action 119 | 120 | public final func startRefreshing(isAuto: Bool = false) -> Void { 121 | guard isRefreshing == false && isAutoRefreshing == false else { 122 | return 123 | } 124 | 125 | _isRefreshing = !isAuto 126 | _isAutoRefreshing = isAuto 127 | 128 | self.start() 129 | } 130 | 131 | public final func stopRefreshing() -> Void { 132 | guard isRefreshing == true || isAutoRefreshing == true else { 133 | return 134 | } 135 | 136 | self.stop() 137 | } 138 | 139 | public func start() { 140 | 141 | } 142 | 143 | public func stop() { 144 | _isRefreshing = false 145 | _isAutoRefreshing = false 146 | } 147 | 148 | // ScrollView contentSize change action 149 | public func sizeChangeAction(object: AnyObject?, change: [NSKeyValueChangeKey : Any]?) { 150 | 151 | } 152 | 153 | // ScrollView offset change action 154 | public func offsetChangeAction(object: AnyObject?, change: [NSKeyValueChangeKey : Any]?) { 155 | 156 | } 157 | 158 | } 159 | 160 | extension ESRefreshComponent /* KVO methods */ { 161 | 162 | fileprivate static var context = "ESRefreshKVOContext" 163 | fileprivate static let offsetKeyPath = "contentOffset" 164 | fileprivate static let contentSizeKeyPath = "contentSize" 165 | 166 | public func ignoreObserver(_ ignore: Bool = false) { 167 | if let scrollView = scrollView { 168 | scrollView.isScrollEnabled = !ignore 169 | } 170 | isIgnoreObserving = ignore 171 | } 172 | 173 | fileprivate func addObserver(_ view: UIView?) { 174 | if let scrollView = view as? UIScrollView, !isObservingScrollView { 175 | scrollView.addObserver(self, forKeyPath: ESRefreshComponent.offsetKeyPath, options: [.initial, .new], context: &ESRefreshComponent.context) 176 | scrollView.addObserver(self, forKeyPath: ESRefreshComponent.contentSizeKeyPath, options: [.initial, .new], context: &ESRefreshComponent.context) 177 | isObservingScrollView = true 178 | } 179 | } 180 | 181 | fileprivate func removeObserver() { 182 | if let scrollView = superview as? UIScrollView, isObservingScrollView { 183 | scrollView.removeObserver(self, forKeyPath: ESRefreshComponent.offsetKeyPath, context: &ESRefreshComponent.context) 184 | scrollView.removeObserver(self, forKeyPath: ESRefreshComponent.contentSizeKeyPath, context: &ESRefreshComponent.context) 185 | isObservingScrollView = false 186 | } 187 | } 188 | 189 | override open func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { 190 | if context == &ESRefreshComponent.context { 191 | guard isUserInteractionEnabled == true && isHidden == false else { 192 | return 193 | } 194 | if keyPath == ESRefreshComponent.contentSizeKeyPath { 195 | if isIgnoreObserving == false { 196 | sizeChangeAction(object: object as AnyObject?, change: change) 197 | } 198 | } else if keyPath == ESRefreshComponent.offsetKeyPath { 199 | if isIgnoreObserving == false { 200 | offsetChangeAction(object: object as AnyObject?, change: change) 201 | } 202 | } 203 | } else { 204 | 205 | } 206 | } 207 | 208 | } 209 | 210 | -------------------------------------------------------------------------------- /EasyListViewExample/Vendor/ESPullToRefresh/ESRefreshProtocol.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ESRefreshProtocol.swift 3 | // 4 | // Created by egg swift on 16/4/7. 5 | // Copyright (c) 2013-2016 ESPullToRefresh (https://github.com/eggswift/pull-to-refresh) 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | // THE SOFTWARE. 24 | // 25 | 26 | import Foundation 27 | import UIKit 28 | 29 | public enum ESRefreshViewState { 30 | case pullToRefresh 31 | case releaseToRefresh 32 | case refreshing 33 | case autoRefreshing 34 | case noMoreData 35 | } 36 | 37 | /** 38 | * ESRefreshProtocol 39 | * Animation event handling callback protocol 40 | * You can customize the refresh or custom animation effects 41 | * Mutating is to be able to modify or enum struct variable in the method - http://swifter.tips/protocol-mutation/ by ONEVCAT 42 | */ 43 | public protocol ESRefreshProtocol { 44 | 45 | /** 46 | Refresh operation begins execution method 47 | You can refresh your animation logic here, it will need to start the animation each time a refresh 48 | */ 49 | mutating func refreshAnimationBegin(view: ESRefreshComponent) 50 | 51 | /** 52 | Refresh operation stop execution method 53 | Here you can reset your refresh control UI, such as a Stop UIImageView animations or some opened Timer refresh, etc., it will be executed once each time the need to end the animation 54 | */ 55 | mutating func refreshAnimationEnd(view: ESRefreshComponent) 56 | 57 | /** 58 | Pulling status callback , progress is the percentage of the current offset with trigger, and avoid doing too many tasks in this process so as not to affect the fluency. 59 | */ 60 | mutating func refresh(view: ESRefreshComponent, progressDidChange progress: CGFloat) 61 | 62 | mutating func refresh(view: ESRefreshComponent, stateDidChange state: ESRefreshViewState) 63 | } 64 | 65 | 66 | public protocol ESRefreshAnimatorProtocol { 67 | 68 | // The view that called when component refresh, returns a custom view or self if 'self' is the customized views. 69 | var view: UIView {get} 70 | 71 | // Customized inset. 72 | var insets: UIEdgeInsets {set get} 73 | 74 | // Refresh event is executed threshold required y offset, set a value greater than 0.0, the default is 60.0 75 | var trigger: CGFloat {set get} 76 | 77 | // Offset y refresh event executed by this parameter you can customize the animation to perform when you refresh the view of reservations height 78 | var executeIncremental: CGFloat {set get} 79 | 80 | // Current refresh state, default is .pullToRefresh 81 | var state: ESRefreshViewState {set get} 82 | 83 | } 84 | 85 | /** 86 | * ESRefreshImpacter 87 | * Support iPhone7/iPhone7 Plus or later feedback impact 88 | * You can confirm the ESRefreshImpactProtocol 89 | */ 90 | fileprivate class ESRefreshImpacter { 91 | static private var impacter: AnyObject? = { 92 | if #available(iOS 10.0, *) { 93 | if NSClassFromString("UIFeedbackGenerator") != nil { 94 | let generator = UIImpactFeedbackGenerator.init(style: .light) 95 | generator.prepare() 96 | return generator 97 | } 98 | } 99 | return nil 100 | }() 101 | 102 | static public func impact() -> Void { 103 | if #available(iOS 10.0, *) { 104 | if let impacter = impacter as? UIImpactFeedbackGenerator { 105 | impacter.impactOccurred() 106 | } 107 | } 108 | } 109 | } 110 | 111 | public protocol ESRefreshImpactProtocol {} 112 | public extension ESRefreshImpactProtocol { 113 | 114 | func impact() -> Void { 115 | ESRefreshImpacter.impact() 116 | } 117 | 118 | } 119 | -------------------------------------------------------------------------------- /EasyListViewExample/Vendor/Stevia/Stevia+Baselines.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Stevia+Baselines.swift 3 | // Stevia 4 | // 5 | // Created by Sacha on 09/09/2018. 6 | // Copyright © 2018 Sacha Durand Saint Omer. All rights reserved. 7 | // 8 | 9 | #if canImport(UIKit) 10 | import UIKit 11 | 12 | /** Aligns an array of views by their lastBaselines (on the Y Axis) 13 | 14 | Example Usage: 15 | ``` 16 | align(lastBaselines: label1, label2, label3) 17 | ``` 18 | 19 | Can also be used directly on horizontal layouts since they return the array of views : 20 | ``` 21 | align(lastBaselines: |-label1-label2-label3-|) 22 | ``` 23 | 24 | - Returns: The array of views, enabling chaining, 25 | 26 | */ 27 | @discardableResult 28 | public func align(lastBaselines views: UIView...) -> [UIView] { 29 | return align(lastBaselines: views) 30 | } 31 | 32 | @discardableResult 33 | public func align(lastBaselines views: [UIView]) -> [UIView] { 34 | for (i, v) in views.enumerated() where views.count > i+1 { 35 | let v2 = views[i+1] 36 | if #available(iOS 9.0, *) { 37 | v.lastBaselineAnchor.constraint(equalTo: v2.lastBaselineAnchor).isActive = true 38 | } else if let spv = v.superview { 39 | let c = constraint(item: v, attribute: .lastBaseline, toItem: v2) 40 | spv.addConstraint(c) 41 | } 42 | } 43 | return views 44 | } 45 | 46 | /** Aligns an array of views by their firstBaselines (on the Y Axis) 47 | 48 | Example Usage: 49 | ``` 50 | align(firstBaselines: label1, label2, label3) 51 | ``` 52 | 53 | Can also be used directly on horizontal layouts since they return the array of views : 54 | ``` 55 | align(firstBaselines: |-label1-label2-label3-|) 56 | ``` 57 | 58 | - Returns: The array of views, enabling chaining, 59 | 60 | */ 61 | @discardableResult 62 | public func align(firstBaselines views: UIView...) -> [UIView] { 63 | return align(firstBaselines: views) 64 | } 65 | 66 | @discardableResult 67 | public func align(firstBaselines views: [UIView]) -> [UIView] { 68 | for (i, v) in views.enumerated() where views.count > i+1 { 69 | let v2 = views[i+1] 70 | if #available(iOS 9.0, *) { 71 | v.firstBaselineAnchor.constraint(equalTo: v2.firstBaselineAnchor).isActive = true 72 | } else if let spv = v.superview { 73 | let c = constraint(item: v, attribute: .firstBaseline, toItem: v2) 74 | spv.addConstraint(c) 75 | } 76 | } 77 | return views 78 | } 79 | #endif 80 | -------------------------------------------------------------------------------- /EasyListViewExample/Vendor/Stevia/Stevia+Center.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Stevia+Center.swift 3 | // Stevia 4 | // 5 | // Created by Sacha Durand Saint Omer on 10/02/16. 6 | // Copyright © 2016 Sacha Durand Saint Omer. All rights reserved. 7 | // 8 | 9 | #if canImport(UIKit) 10 | import UIKit 11 | 12 | public extension UIView { 13 | 14 | /** 15 | Centers the view in its container. 16 | 17 | ``` 18 | button.centerInContainer() 19 | ``` 20 | 21 | - Returns: Itself, enabling chaining, 22 | 23 | */ 24 | @discardableResult 25 | func centerInContainer() -> Self { 26 | if let spv = superview { 27 | alignCenter(self, with: spv) 28 | } 29 | return self 30 | } 31 | 32 | /** 33 | Centers the view horizontally (X axis) in its container. 34 | 35 | ``` 36 | button.centerHorizontally() 37 | button.centerHorizontally(offset: 40) 38 | ``` 39 | 40 | - Returns: Itself, enabling chaining, 41 | 42 | */ 43 | @discardableResult 44 | func centerHorizontally(offset: Double = 0) -> Self { 45 | if let spv = superview { 46 | align(.vertical, v1: self, with: spv, offset: offset) 47 | } 48 | return self 49 | } 50 | 51 | /** 52 | Centers the view horizontally (X axis) in its container. 53 | 54 | ``` 55 | button.centerHorizontally() 56 | button.centerHorizontally(offset: 40) 57 | ``` 58 | 59 | - Returns: Itself, enabling chaining, 60 | 61 | */ 62 | @discardableResult 63 | func centerHorizontally(offset: CGFloat) -> Self { 64 | centerHorizontally(offset: Double(offset)) 65 | } 66 | 67 | /** 68 | Centers the view horizontally (X axis) in its container. 69 | 70 | ``` 71 | button.centerHorizontally() 72 | button.centerHorizontally(offset: 40) 73 | ``` 74 | 75 | - Returns: Itself, enabling chaining, 76 | 77 | */ 78 | @discardableResult 79 | func centerHorizontally(offset: Int) -> Self { 80 | centerHorizontally(offset: Double(offset)) 81 | } 82 | 83 | /** 84 | Centers the view vertically (Y axis) in its container. 85 | 86 | ``` 87 | button.centerVertically() 88 | button.centerVertically(offset: 40) 89 | ``` 90 | 91 | - Returns: Itself, enabling chaining, 92 | 93 | */ 94 | @discardableResult 95 | func centerVertically(offset: Double = 0) -> Self { 96 | if let spv = superview { 97 | align(.horizontal, v1: self, with: spv, offset: offset) 98 | } 99 | return self 100 | } 101 | 102 | /** 103 | Centers the view vertically (Y axis) in its container. 104 | 105 | ``` 106 | button.centerVertically() 107 | button.centerVertically(offset: 40) 108 | ``` 109 | 110 | - Returns: Itself, enabling chaining, 111 | 112 | */ 113 | @discardableResult 114 | func centerVertically(offset: CGFloat) -> Self { 115 | centerVertically(offset: Double(offset)) 116 | } 117 | 118 | /** 119 | Centers the view vertically (Y axis) in its container. 120 | 121 | ``` 122 | button.centerVertically() 123 | button.centerVertically(offset: 40) 124 | ``` 125 | 126 | - Returns: Itself, enabling chaining, 127 | 128 | */ 129 | @discardableResult 130 | func centerVertically(offset: Int) -> Self { 131 | centerVertically(offset: Double(offset)) 132 | } 133 | } 134 | #endif 135 | -------------------------------------------------------------------------------- /EasyListViewExample/Vendor/Stevia/Stevia+Constraints.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Stevia+Constraints.swift 3 | // LoginNadir 4 | // 5 | // Created by Sacha Durand Saint Omer on 01/10/15. 6 | // Copyright © 2015 Sacha Durand Saint Omer. All rights reserved. 7 | // 8 | 9 | #if canImport(UIKit) 10 | import UIKit 11 | 12 | // MARK: - Shortcut 13 | 14 | public extension UIView { 15 | 16 | /** 17 | Helper for creating and adding NSLayoutConstraint but with default values provided. 18 | 19 | For instance 20 | 21 | addConstraint(item: view1, attribute: .CenterX, toItem: view2) 22 | 23 | is equivalent to 24 | 25 | addConstraint( 26 | NSLayoutConstraint(item: view1, 27 | attribute: .CenterX, 28 | relatedBy: .Equal, 29 | toItem: view2, 30 | attribute: .CenterX, 31 | multiplier: 1, 32 | constant: 0 33 | ) 34 | ) 35 | 36 | - Returns: The NSLayoutConstraint created. 37 | */ 38 | @discardableResult 39 | func addConstraint(item view1: AnyObject, 40 | attribute attr1: NSLayoutConstraint.Attribute, 41 | relatedBy: NSLayoutConstraint.Relation = .equal, 42 | toItem view2: AnyObject? = nil, 43 | attribute attr2: NSLayoutConstraint.Attribute? = nil, 44 | multiplier: Double = 1, 45 | constant: Double = 0) -> NSLayoutConstraint { 46 | let c = constraint( 47 | item: view1, attribute: attr1, 48 | relatedBy: relatedBy, 49 | toItem: view2, attribute: attr2, 50 | multiplier: multiplier, constant: constant) 51 | addConstraint(c) 52 | return c 53 | } 54 | } 55 | 56 | /** 57 | Helper for creating a NSLayoutConstraint but with default values provided. 58 | 59 | For instance 60 | 61 | constraint(item: view1, attribute: .CenterX, toItem: view2) 62 | 63 | is equivalent to 64 | 65 | NSLayoutConstraint(item: view1, attribute: .CenterX, 66 | relatedBy: .Equal, 67 | toItem: view2, attribute: .CenterX, 68 | multiplier: 1, constant: 0) 69 | 70 | - Returns: The NSLayoutConstraint created. 71 | */ 72 | func constraint(item view1: AnyObject, 73 | attribute attr1: NSLayoutConstraint.Attribute, 74 | relatedBy: NSLayoutConstraint.Relation = .equal, 75 | toItem view2: AnyObject? = nil, 76 | attribute attr2: NSLayoutConstraint.Attribute? = nil, // Not an attribute?? 77 | multiplier: Double = 1, 78 | constant: Double = 0) -> NSLayoutConstraint { 79 | let c = NSLayoutConstraint(item: view1, attribute: attr1, 80 | relatedBy: relatedBy, 81 | toItem: view2, attribute: ((attr2 == nil) ? attr1 : attr2! ), 82 | multiplier: CGFloat(multiplier), constant: CGFloat(constant)) 83 | c.priority = UILayoutPriority(rawValue: UILayoutPriority.defaultHigh.rawValue + 1) 84 | return c 85 | } 86 | 87 | public extension UIView { 88 | 89 | /** 90 | Get User added constraints. For making complex changes on layout, we need to remove user added constraints. 91 | 92 | If we remove all constraints, it may return broken layout. 93 | 94 | Use this method as: 95 | 96 | removeConstraints(userAddedConstraints) 97 | 98 | */ 99 | var userAddedConstraints: [NSLayoutConstraint] { 100 | return constraints.filter { c in 101 | guard let cId = c.identifier else { return true } 102 | return !cId.contains("UIView-Encapsulated-Layout") && !cId.contains("Margin-guide-constraint") 103 | } 104 | } 105 | } 106 | 107 | // MARK: - Other 108 | 109 | public extension UIView { 110 | 111 | /** 112 | Makes a view follow another view's frame. 113 | For instance if we want a button to be on top of an image : 114 | 115 | ``` 116 | button.followEdges(image) 117 | ``` 118 | */ 119 | func followEdges(_ otherView: UIView) { 120 | if let spv = superview { 121 | let cs = [ 122 | constraint(item: self, attribute: .top, toItem: otherView), 123 | constraint(item: self, attribute: .trailing, toItem: otherView), 124 | constraint(item: self, attribute: .bottom, toItem: otherView), 125 | constraint(item: self, attribute: .leading, toItem: otherView) 126 | ] 127 | spv.addConstraints(cs) 128 | } 129 | } 130 | 131 | /** 132 | Enforce a view to keep height and width equal at all times, essentially 133 | forcing it to be a square. 134 | 135 | ``` 136 | image.heightEqualsWidth() 137 | ``` 138 | 139 | - Returns: Itself, enabling chaining, 140 | 141 | */ 142 | @discardableResult 143 | func heightEqualsWidth() -> Self { 144 | if let spv = superview { 145 | let c = constraint(item: self, attribute: .height, toItem: self, attribute: .width) 146 | spv.addConstraint(c) 147 | } 148 | return self 149 | } 150 | 151 | } 152 | #endif 153 | -------------------------------------------------------------------------------- /EasyListViewExample/Vendor/Stevia/Stevia+Content.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Stevia+Content.swift 3 | // LoginStevia 4 | // 5 | // Created by Sacha Durand Saint Omer on 01/10/15. 6 | // Copyright © 2015 Sacha Durand Saint Omer. All rights reserved. 7 | // 8 | 9 | #if canImport(UIKit) 10 | import UIKit 11 | 12 | public extension UIButton { 13 | 14 | /** 15 | Sets the title of the button for normal State 16 | 17 | Essentially a shortcut for `setTitle("MyText", forState: .Normal)` 18 | 19 | - Returns: Itself for chaining purposes 20 | */ 21 | @discardableResult 22 | func text(_ t: String) -> Self { 23 | setTitle(t, for: .normal) 24 | return self 25 | } 26 | 27 | /** 28 | Sets the localized key for the button's title in normal State 29 | 30 | Essentially a shortcut for `setTitle(NSLocalizedString("MyText", comment: "") 31 | , forState: .Normal)` 32 | 33 | - Returns: Itself for chaining purposes 34 | */ 35 | @discardableResult 36 | func textKey(_ t: String) -> Self { 37 | text(NSLocalizedString(t, comment: "")) 38 | return self 39 | } 40 | 41 | /** 42 | Sets the image of the button in normal State 43 | 44 | Essentially a shortcut for `setImage(UIImage(named:"X"), forState: .Normal)` 45 | 46 | - Returns: Itself for chaining purposes 47 | */ 48 | @discardableResult 49 | func image(_ s: String) -> Self { 50 | setImage(UIImage(named: s), for: .normal) 51 | return self 52 | } 53 | } 54 | 55 | public extension UITextField { 56 | /** 57 | Sets the textfield placeholder but in a chainable fashion 58 | - Returns: Itself for chaining purposes 59 | */ 60 | @discardableResult 61 | func placeholder(_ t: String) -> Self { 62 | placeholder = t 63 | return self 64 | } 65 | } 66 | 67 | public extension UILabel { 68 | /** 69 | Sets the label text but in a chainable fashion 70 | - Returns: Itself for chaining purposes 71 | */ 72 | @discardableResult 73 | func text(_ t: String) -> Self { 74 | text = t 75 | return self 76 | } 77 | 78 | /** 79 | Sets the label localization key but in a chainable fashion 80 | Essentially a shortcut for `text = NSLocalizedString("X", comment: "")` 81 | - Returns: Itself for chaining purposes 82 | */ 83 | @discardableResult 84 | func textKey(_ t: String) -> Self { 85 | text(NSLocalizedString(t, comment: "")) 86 | return self 87 | } 88 | } 89 | 90 | extension UIImageView { 91 | /** 92 | Sets the image of the imageView but in a chainable fashion 93 | 94 | Essentially a shortcut for `image = UIImage(named: "X")` 95 | 96 | - Returns: Itself for chaining purposes 97 | */ 98 | @discardableResult 99 | public func image(_ t: String) -> Self { 100 | image = UIImage(named: t) 101 | return self 102 | } 103 | } 104 | #endif 105 | -------------------------------------------------------------------------------- /EasyListViewExample/Vendor/Stevia/Stevia+DoubleDash.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Stevia+DoubleDash.swift 3 | // Stevia 4 | // 5 | // Created by Sacha Durand Saint Omer on 03/05/16. 6 | // Copyright © 2016 Sacha Durand Saint Omer. All rights reserved. 7 | // 8 | 9 | #if canImport(UIKit) 10 | import UIKit 11 | 12 | infix operator -- :AdditionPrecedence 13 | 14 | 15 | @available(*, deprecated, renamed: "⁃") 16 | @discardableResult 17 | public func -- (left: UIView, right: Double) -> PartialConstraint { 18 | return left-right 19 | } 20 | 21 | @available(*, deprecated, renamed: "⁃") 22 | public func -- (left: UIView, right: CGFloat) -> PartialConstraint { 23 | return left--Double(right) 24 | } 25 | 26 | @available(*, deprecated, renamed: "⁃") 27 | public func -- (left: UIView, right: Int) -> PartialConstraint { 28 | return left--Double(right) 29 | } 30 | 31 | @available(*, deprecated, renamed: "⁃") 32 | @discardableResult 33 | public func -- (left: SideConstraint, right: UIView) -> UIView { 34 | return left-right 35 | } 36 | 37 | @available(*, deprecated, renamed: "⁃") 38 | @discardableResult 39 | public func -- (left: [UIView], right: SideConstraint) -> [UIView] { 40 | return left-right 41 | } 42 | 43 | @available(*, deprecated, renamed: "⁃") 44 | @discardableResult 45 | public func -- (left: UIView, right: SideConstraint) -> UIView { 46 | return left-right 47 | } 48 | 49 | @available(*, deprecated, renamed: "⁃") 50 | @discardableResult 51 | public func -- (left: PartialConstraint, right: UIView) -> [UIView] { 52 | return left-right 53 | } 54 | 55 | @available(*, deprecated, renamed: "⁃") 56 | @discardableResult 57 | public func -- (left: UIView, right: UIView) -> [UIView] { 58 | return left-right 59 | } 60 | 61 | @available(*, deprecated, renamed: "⁃") 62 | @discardableResult 63 | public func -- (left: [UIView], right: Double) -> PartialConstraint { 64 | return left-right 65 | } 66 | 67 | @available(*, deprecated, renamed: "⁃") 68 | @discardableResult 69 | public func -- (left: [UIView], right: CGFloat) -> PartialConstraint { 70 | return left--Double(right) 71 | } 72 | 73 | @available(*, deprecated, renamed: "⁃") 74 | @discardableResult 75 | public func -- (left: [UIView], right: Int) -> PartialConstraint { 76 | return left--Double(right) 77 | } 78 | 79 | @available(*, deprecated, renamed: "⁃") 80 | @discardableResult 81 | public func -- (left: [UIView], right: UIView) -> [UIView] { 82 | return left-right 83 | } 84 | 85 | @available(*, deprecated, renamed: "⁃") 86 | @discardableResult 87 | public func -- (left: UIView, right: String) -> Space { 88 | return left-right 89 | } 90 | 91 | @available(*, deprecated, renamed: "⁃") 92 | @discardableResult 93 | public func -- (left: [UIView], right: String) -> Space { 94 | return left-right 95 | } 96 | 97 | @available(*, deprecated, renamed: "⁃") 98 | @discardableResult 99 | public func -- (left: Space, right: UIView) -> [UIView] { 100 | return left-right 101 | } 102 | 103 | @available(*, deprecated, renamed: "⁃") 104 | @discardableResult 105 | public func -- (left: UIView, 106 | right: SteviaFlexibleMargin) -> PartialFlexibleConstraint { 107 | return left-right 108 | } 109 | 110 | @available(*, deprecated, renamed: "⁃") 111 | @discardableResult 112 | public func -- (left: [UIView], 113 | right: SteviaFlexibleMargin) -> PartialFlexibleConstraint { 114 | return left-right 115 | } 116 | 117 | @available(*, deprecated, renamed: "⁃") 118 | @discardableResult 119 | public func -- (left: PartialFlexibleConstraint, right: UIView) -> [UIView] { 120 | return left-right 121 | } 122 | 123 | @available(*, deprecated, renamed: "⁃") 124 | @discardableResult 125 | public func -- (left: SteviaLeftFlexibleMargin, right: UIView) -> UIView { 126 | return left-right 127 | } 128 | 129 | @available(*, deprecated, renamed: "⁃") 130 | @discardableResult 131 | public func -- (left: UIView, right: SteviaRightFlexibleMargin) -> UIView { 132 | return left-right 133 | } 134 | 135 | @available(*, deprecated, renamed: "⁃") 136 | @discardableResult 137 | public func -- (left: [UIView], right: SteviaRightFlexibleMargin) -> [UIView] { 138 | return left-right 139 | } 140 | #endif 141 | 142 | /// Hyphen Bullet operator is introduced to remove the ambiguity with the language "-" (minus) sign operator. 143 | /// this ambiguity can make tu build times exponentially longer in big layout blocks. 144 | 145 | 146 | #if canImport(UIKit) 147 | import UIKit 148 | 149 | infix operator ⁃ :AdditionPrecedence 150 | 151 | @discardableResult 152 | public func ⁃ (left: UIView, right: Double) -> PartialConstraint { 153 | return left-right 154 | } 155 | 156 | public func ⁃ (left: UIView, right: CGFloat) -> PartialConstraint { 157 | return left⁃Double(right) 158 | } 159 | 160 | public func ⁃ (left: UIView, right: Int) -> PartialConstraint { 161 | return left⁃Double(right) 162 | } 163 | 164 | @discardableResult 165 | public func ⁃ (left: SideConstraint, right: UIView) -> UIView { 166 | return left-right 167 | } 168 | 169 | @discardableResult 170 | public func ⁃ (left: [UIView], right: SideConstraint) -> [UIView] { 171 | return left-right 172 | } 173 | 174 | @discardableResult 175 | public func ⁃ (left: UIView, right: SideConstraint) -> UIView { 176 | return left-right 177 | } 178 | 179 | @discardableResult 180 | public func ⁃ (left: PartialConstraint, right: UIView) -> [UIView] { 181 | return left-right 182 | } 183 | 184 | @discardableResult 185 | public func ⁃ (left: UIView, right: UIView) -> [UIView] { 186 | return left-right 187 | } 188 | 189 | @discardableResult 190 | public func ⁃ (left: [UIView], right: Double) -> PartialConstraint { 191 | return left-right 192 | } 193 | 194 | @discardableResult 195 | public func ⁃ (left: [UIView], right: CGFloat) -> PartialConstraint { 196 | return left⁃Double(right) 197 | } 198 | 199 | @discardableResult 200 | public func ⁃ (left: [UIView], right: Int) -> PartialConstraint { 201 | return left⁃Double(right) 202 | } 203 | 204 | @discardableResult 205 | public func ⁃ (left: [UIView], right: UIView) -> [UIView] { 206 | return left-right 207 | } 208 | 209 | @discardableResult 210 | public func ⁃ (left: UIView, right: String) -> Space { 211 | return left-right 212 | } 213 | 214 | @discardableResult 215 | public func ⁃ (left: [UIView], right: String) -> Space { 216 | return left-right 217 | } 218 | 219 | @discardableResult 220 | public func ⁃ (left: Space, right: UIView) -> [UIView] { 221 | return left-right 222 | } 223 | 224 | @discardableResult 225 | public func ⁃ (left: UIView, 226 | right: SteviaFlexibleMargin) -> PartialFlexibleConstraint { 227 | return left-right 228 | } 229 | 230 | @discardableResult 231 | public func ⁃ (left: [UIView], 232 | right: SteviaFlexibleMargin) -> PartialFlexibleConstraint { 233 | return left-right 234 | } 235 | 236 | @discardableResult 237 | public func ⁃ (left: PartialFlexibleConstraint, right: UIView) -> [UIView] { 238 | return left-right 239 | } 240 | 241 | @discardableResult 242 | public func ⁃ (left: SteviaLeftFlexibleMargin, right: UIView) -> UIView { 243 | return left-right 244 | } 245 | 246 | @discardableResult 247 | public func ⁃ (left: UIView, right: SteviaRightFlexibleMargin) -> UIView { 248 | return left-right 249 | } 250 | 251 | @discardableResult 252 | public func ⁃ (left: [UIView], right: SteviaRightFlexibleMargin) -> [UIView] { 253 | return left-right 254 | } 255 | #endif 256 | -------------------------------------------------------------------------------- /EasyListViewExample/Vendor/Stevia/Stevia+Fill.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Stevia+Fill.swift 3 | // Stevia 4 | // 5 | // Created by Sacha Durand Saint Omer on 10/02/16. 6 | // Copyright © 2016 Sacha Durand Saint Omer. All rights reserved. 7 | // 8 | 9 | #if canImport(UIKit) 10 | import UIKit 11 | 12 | public extension UIView { 13 | 14 | /** 15 | Adds the constraints needed for the view to fill its `superview`. 16 | A padding can be used to apply equal spaces between the view and its superview 17 | */ 18 | @discardableResult 19 | func fillContainer(padding: Double = 0) -> Self { 20 | fillHorizontally(padding: padding) 21 | fillVertically(padding: padding) 22 | return self 23 | } 24 | 25 | /** 26 | Adds the constraints needed for the view to fill its `superview`. 27 | A padding can be used to apply equal spaces between the view and its superview 28 | */ 29 | @discardableResult 30 | func fillContainer(padding: CGFloat) -> Self { 31 | fillContainer(padding: Double(padding)) 32 | } 33 | 34 | /** 35 | Adds the constraints needed for the view to fill its `superview`. 36 | A padding can be used to apply equal spaces between the view and its superview 37 | */ 38 | @discardableResult 39 | func fillContainer(padding: Int) -> Self { 40 | fillContainer(padding: Double(padding)) 41 | } 42 | 43 | /** 44 | Adds the constraints needed for the view to fill its `superview` Vertically. 45 | A padding can be used to apply equal spaces between the view and its superview 46 | */ 47 | @discardableResult 48 | func fillVertically(padding: Double = 0) -> Self { 49 | fill(.vertical, points: padding) 50 | } 51 | 52 | /** 53 | Adds the constraints needed for the view to fill its `superview` Vertically. 54 | A padding can be used to apply equal spaces between the view and its superview 55 | */ 56 | @discardableResult 57 | func fillVertically(padding: CGFloat) -> Self { 58 | fillVertically(padding: Double(padding)) 59 | } 60 | 61 | /** 62 | Adds the constraints needed for the view to fill its `superview` Vertically. 63 | A padding can be used to apply equal spaces between the view and its superview 64 | */ 65 | @discardableResult 66 | func fillVertically(padding: Int) -> Self { 67 | fillVertically(padding: Double(padding)) 68 | } 69 | 70 | /** 71 | Adds the constraints needed for the view to fill its `superview` Horizontally. 72 | A padding can be used to apply equal spaces between the view and its superview 73 | */ 74 | @discardableResult 75 | func fillHorizontally(padding: Double = 0) -> Self { 76 | fill(.horizontal, points: padding) 77 | } 78 | 79 | /** 80 | Adds the constraints needed for the view to fill its `superview` Horizontally. 81 | A padding can be used to apply equal spaces between the view and its superview 82 | */ 83 | @discardableResult 84 | func fillHorizontally(padding: CGFloat) -> Self { 85 | fillHorizontally(padding: Double(padding)) 86 | } 87 | 88 | /** 89 | Adds the constraints needed for the view to fill its `superview` Horizontally. 90 | A padding can be used to apply equal spaces between the view and its superview 91 | */ 92 | @discardableResult 93 | func fillHorizontally(padding: Int) -> Self { 94 | fillHorizontally(padding: Double(padding)) 95 | } 96 | 97 | fileprivate func fill(_ axis: NSLayoutConstraint.Axis, points: Double = 0) -> Self { 98 | let a: NSLayoutConstraint.Attribute = axis == .vertical ? .top : .leading 99 | let b: NSLayoutConstraint.Attribute = axis == .vertical ? .bottom : .trailing 100 | if let spv = superview { 101 | let c1 = constraint(item: self, attribute: a, toItem: spv, constant: points) 102 | let c2 = constraint(item: self, attribute: b, toItem: spv, constant: -points) 103 | spv.addConstraints([c1, c2]) 104 | } 105 | return self 106 | } 107 | } 108 | #endif 109 | -------------------------------------------------------------------------------- /EasyListViewExample/Vendor/Stevia/Stevia+FlexibleMargin.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Stevia+FlexibleMargin.swift 3 | // Stevia 4 | // 5 | // Created by Sacha Durand Saint Omer on 10/07/16. 6 | // Copyright © 2016 Sacha Durand Saint Omer. All rights reserved. 7 | // 8 | 9 | #if canImport(UIKit) 10 | import UIKit 11 | 12 | prefix operator >= 13 | @discardableResult 14 | public prefix func >= (p: Double) -> SteviaFlexibleMargin { 15 | SteviaFlexibleMargin(points: p, relation: .greaterThanOrEqual) 16 | } 17 | 18 | @discardableResult 19 | public prefix func >= (p: CGFloat) -> SteviaFlexibleMargin { 20 | >=Double(p) 21 | } 22 | 23 | @discardableResult 24 | public prefix func >= (p: Int) -> SteviaFlexibleMargin { 25 | >=Double(p) 26 | } 27 | 28 | prefix operator <= 29 | @discardableResult 30 | public prefix func <= (p: Double) -> SteviaFlexibleMargin { 31 | SteviaFlexibleMargin(points: p, relation: .lessThanOrEqual) 32 | } 33 | 34 | @discardableResult 35 | public prefix func <= (p: CGFloat) -> SteviaFlexibleMargin { 36 | <=Double(p) 37 | } 38 | 39 | @discardableResult 40 | public prefix func <= (p: Int) -> SteviaFlexibleMargin { 41 | <=Double(p) 42 | } 43 | 44 | public struct SteviaFlexibleMargin { 45 | var points: Double! 46 | var relation: NSLayoutConstraint.Relation! 47 | } 48 | 49 | public struct PartialFlexibleConstraint { 50 | var fm: SteviaFlexibleMargin! 51 | var view1: UIView? 52 | var views: [UIView]? 53 | } 54 | 55 | @discardableResult 56 | public func - (left: UIView, 57 | right: SteviaFlexibleMargin) -> PartialFlexibleConstraint { 58 | return PartialFlexibleConstraint(fm: right, view1: left, views: nil) 59 | } 60 | 61 | @discardableResult 62 | public func - (left: [UIView], 63 | right: SteviaFlexibleMargin) -> PartialFlexibleConstraint { 64 | return PartialFlexibleConstraint(fm: right, view1: nil, views: left) 65 | } 66 | 67 | @discardableResult 68 | public func - (left: PartialFlexibleConstraint, right: UIView) -> [UIView] { 69 | if let views = left.views { 70 | if let spv = right.superview { 71 | let c = constraint(item: right, attribute: .leading, 72 | relatedBy: left.fm.relation, toItem: views.last, 73 | attribute: .trailing, 74 | constant: left.fm.points) 75 | spv.addConstraint(c) 76 | } 77 | return views + [right] 78 | } else { 79 | if let spv = right.superview { 80 | let c = constraint(item: right, attribute: .leading, 81 | relatedBy: left.fm.relation, toItem: left.view1!, 82 | attribute: .trailing, 83 | constant: left.fm.points) 84 | spv.addConstraint(c) 85 | } 86 | return [left.view1!, right] 87 | } 88 | } 89 | 90 | // Left Flexible margin 91 | 92 | public struct SteviaLeftFlexibleMargin { 93 | let fm: SteviaFlexibleMargin 94 | } 95 | 96 | @discardableResult 97 | public prefix func |- (fm: SteviaFlexibleMargin) -> SteviaLeftFlexibleMargin { 98 | return SteviaLeftFlexibleMargin(fm: fm) 99 | } 100 | 101 | @discardableResult 102 | public func - (left: SteviaLeftFlexibleMargin, right: UIView) -> UIView { 103 | if let spv = right.superview { 104 | let c = constraint(item: right, attribute: .leading, 105 | relatedBy: left.fm.relation, toItem: spv, 106 | attribute: .leading, 107 | constant: left.fm.points) 108 | spv.addConstraint(c) 109 | } 110 | return right 111 | } 112 | 113 | // Right Flexible margin 114 | 115 | public struct SteviaRightFlexibleMargin { 116 | let fm: SteviaFlexibleMargin 117 | } 118 | 119 | @discardableResult 120 | public postfix func -| (fm: SteviaFlexibleMargin) -> SteviaRightFlexibleMargin { 121 | return SteviaRightFlexibleMargin(fm: fm) 122 | } 123 | 124 | @discardableResult 125 | public func - (left: UIView, right: SteviaRightFlexibleMargin) -> UIView { 126 | if let spv = left.superview { 127 | let c = constraint(item: spv, attribute: .trailing, 128 | relatedBy: right.fm.relation, toItem: left, 129 | attribute: .trailing, 130 | constant: right.fm.points) 131 | spv.addConstraint(c) 132 | } 133 | return left 134 | } 135 | 136 | @discardableResult 137 | public func - (left: [UIView], right: SteviaRightFlexibleMargin) -> [UIView] { 138 | if let spv = left.last!.superview { 139 | let c = constraint(item: spv, attribute: .trailing, 140 | relatedBy: right.fm.relation, 141 | toItem: left.last!, 142 | attribute: .trailing, 143 | constant: right.fm.points) 144 | spv.addConstraint(c) 145 | } 146 | return left 147 | } 148 | #endif 149 | -------------------------------------------------------------------------------- /EasyListViewExample/Vendor/Stevia/Stevia+GetConstraint.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Stevia+GetConstraint.swift 3 | // Stevia 4 | // 5 | // Created by Sacha Durand Saint Omer on 12/03/16. 6 | // Copyright © 2016 Sacha Durand Saint Omer. All rights reserved. 7 | // 8 | 9 | #if canImport(UIKit) 10 | import UIKit 11 | 12 | public extension UIView { 13 | 14 | /** Gets the left constraint if found. 15 | 16 | Example Usage for changing left margin of a label : 17 | ``` 18 | label.leftConstraint?.constant = 10 19 | 20 | // Animate if needed 21 | UIView.animateWithDuration(0.3, animations:layoutIfNeeded) 22 | ``` 23 | - Returns: The left NSLayoutConstraint if found. 24 | */ 25 | var leftConstraint: NSLayoutConstraint? { 26 | return constraintForView(self, attribute: .left) 27 | } 28 | 29 | /** Gets the right constraint if found. 30 | 31 | Example Usage for changing right margin of a label : 32 | 33 | ``` 34 | label.rightConstraint?.constant = 10 35 | 36 | // Animate if needed 37 | UIView.animateWithDuration(0.3, animations:layoutIfNeeded) 38 | ``` 39 | - Returns: The right NSLayoutConstraint if found. 40 | */ 41 | var rightConstraint: NSLayoutConstraint? { 42 | return constraintForView(self, attribute: .right) 43 | } 44 | 45 | /** Gets the top constraint if found. 46 | 47 | Example Usage for changing top margin of a label : 48 | 49 | ``` 50 | label.topConstraint?.constant = 10 51 | 52 | // Animate if needed 53 | UIView.animateWithDuration(0.3, animations:layoutIfNeeded) 54 | ``` 55 | - Returns: The top NSLayoutConstraint if found. 56 | */ 57 | var topConstraint: NSLayoutConstraint? { 58 | return constraintForView(self, attribute: .top) 59 | } 60 | 61 | /** Gets the bottom constraint if found. 62 | 63 | Example Usage for changing bottom margin of a label : 64 | 65 | ``` 66 | label.bottomConstraint?.constant = 10 67 | 68 | // Animate if needed 69 | UIView.animateWithDuration(0.3, animations:layoutIfNeeded) 70 | ``` 71 | - Returns: The bottom NSLayoutConstraint if found. 72 | */ 73 | var bottomConstraint: NSLayoutConstraint? { 74 | return constraintForView(self, attribute: .bottom) 75 | } 76 | 77 | /** Gets the height constraint if found. 78 | 79 | Example Usage for changing height property of a label : 80 | 81 | ``` 82 | label.heightConstraint?.constant = 10 83 | 84 | // Animate if needed 85 | UIView.animateWithDuration(0.3, animations:layoutIfNeeded) 86 | ``` 87 | - Returns: The height NSLayoutConstraint if found. 88 | */ 89 | var heightConstraint: NSLayoutConstraint? { 90 | return constraintForView(self, attribute: .height) 91 | } 92 | 93 | /** Gets the width constraint if found. 94 | 95 | Example Usage for changing width property of a label : 96 | 97 | ``` 98 | label.widthConstraint?.constant = 10 99 | 100 | // Animate if needed 101 | UIView.animateWithDuration(0.3, animations:layoutIfNeeded) 102 | ``` 103 | - Returns: The width NSLayoutConstraint if found. 104 | */ 105 | var widthConstraint: NSLayoutConstraint? { 106 | return constraintForView(self, attribute: .width) 107 | } 108 | 109 | /** Gets the trailing constraint if found. 110 | 111 | Example Usage for changing width property of a label : 112 | 113 | ``` 114 | label.trailingConstraint?.constant = 10 115 | 116 | // Animate if needed 117 | UIView.animateWithDuration(0.3, animations:layoutIfNeeded) 118 | ``` 119 | - Returns: The trailing NSLayoutConstraint if found. 120 | */ 121 | var trailingConstraint: NSLayoutConstraint? { 122 | return constraintForView(self, attribute: .trailing) 123 | } 124 | 125 | /** Gets the leading constraint if found. 126 | 127 | Example Usage for changing width property of a label : 128 | 129 | ``` 130 | label.leadingConstraint?.constant = 10 131 | 132 | // Animate if needed 133 | UIView.animateWithDuration(0.3, animations:layoutIfNeeded) 134 | ``` 135 | - Returns: The leading NSLayoutConstraint if found. 136 | */ 137 | var leadingConstraint: NSLayoutConstraint? { 138 | return constraintForView(self, attribute: .leading) 139 | } 140 | 141 | /** Gets the centerX constraint if found. 142 | 143 | Example Usage for changing width property of a label : 144 | 145 | ``` 146 | label.centerXConstraint?.constant = 10 147 | 148 | // Animate if needed 149 | UIView.animateWithDuration(0.3, animations:layoutIfNeeded) 150 | ``` 151 | - Returns: The width NSLayoutConstraint if found. 152 | */ 153 | var centerXConstraint: NSLayoutConstraint? { 154 | return constraintForView(self, attribute: .centerX) 155 | } 156 | 157 | /** Gets the centerY constraint if found. 158 | 159 | Example Usage for changing width property of a label : 160 | 161 | ``` 162 | label.centerYConstraint?.constant = 10 163 | 164 | // Animate if needed 165 | UIView.animateWithDuration(0.3, animations:layoutIfNeeded) 166 | ``` 167 | - Returns: The width NSLayoutConstraint if found. 168 | */ 169 | var centerYConstraint: NSLayoutConstraint? { 170 | return constraintForView(self, attribute: .centerY) 171 | } 172 | } 173 | 174 | func constraintForView(_ v: UIView, attribute: NSLayoutConstraint.Attribute) -> NSLayoutConstraint? { 175 | 176 | func lookForConstraint(in view: UIView?) -> NSLayoutConstraint? { 177 | guard let constraints = view?.constraints else { 178 | return nil 179 | } 180 | for c in constraints { 181 | if let fi = c.firstItem as? NSObject, fi == v && c.firstAttribute == attribute { 182 | return c 183 | } else if let si = c.secondItem as? NSObject, si == v && c.secondAttribute == attribute { 184 | return c 185 | } 186 | } 187 | return nil 188 | } 189 | 190 | // Width and height constraints added via widthAnchor/heightAnchors are 191 | // added on the view itself. 192 | if (attribute == .width || attribute == .height) { 193 | return lookForConstraint(in: v.superview) ?? lookForConstraint(in: v) 194 | } 195 | 196 | // Look for constraint on superview. 197 | return lookForConstraint(in: v.superview) 198 | } 199 | #endif 200 | -------------------------------------------------------------------------------- /EasyListViewExample/Vendor/Stevia/Stevia+Hierarchy.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Stevia+Hierarchy.swift 3 | // LoginStevia 4 | // 5 | // Created by Sacha Durand Saint Omer on 01/10/15. 6 | // Copyright © 2015 Sacha Durand Saint Omer. All rights reserved. 7 | // 8 | 9 | #if canImport(UIKit) 10 | import UIKit 11 | 12 | @_functionBuilder public struct SubviewsBuilder { 13 | public static func buildBlock(_ content: UIView...) -> [UIView] { 14 | return content 15 | } 16 | } 17 | 18 | public extension UIView { 19 | 20 | @available(*, deprecated, renamed: "subviews") 21 | @discardableResult 22 | func sv(_ subViews: UIView...) -> UIView { 23 | subviews(subViews) 24 | } 25 | 26 | /** 27 | Defines the view hierachy for the view. 28 | 29 | Esentially, this is just a shortcut to `addSubview` 30 | and 'translatesAutoresizingMaskIntoConstraints = false' 31 | 32 | 33 | 34 | ``` 35 | class MyView: UIView { 36 | 37 | let email = UITextField() 38 | let password = UITextField() 39 | let login = UIButton() 40 | 41 | convenience init() { 42 | self.init(frame: CGRect.zero) 43 | 44 | subviews( 45 | email, 46 | password, 47 | login 48 | ) 49 | ... 50 | 51 | } 52 | } 53 | 54 | ``` 55 | 56 | - Returns: Itself to enable nested layouts. 57 | */ 58 | @discardableResult 59 | func subviews(_ subViews: UIView...) -> UIView { 60 | subviews(subViews) 61 | } 62 | 63 | /** 64 | Defines the view hierachy for the view. 65 | 66 | Esentially, this is just a shortcut to `addSubview` 67 | and 'translatesAutoresizingMaskIntoConstraints = false' 68 | 69 | 70 | 71 | ``` 72 | class MyView: UIView { 73 | 74 | let email = UITextField() 75 | let password = UITextField() 76 | let login = UIButton() 77 | 78 | convenience init() { 79 | self.init(frame: CGRect.zero) 80 | 81 | subviews { 82 | email 83 | password 84 | login 85 | } 86 | ... 87 | 88 | } 89 | } 90 | 91 | ``` 92 | 93 | - Returns: Itself to enable nested layouts. 94 | */ 95 | @discardableResult 96 | func subviews(@SubviewsBuilder content: () -> [UIView]) -> UIView { 97 | subviews(content()) 98 | } 99 | 100 | /** 101 | Defines the view hierachy for the view. 102 | 103 | Esentially, this is just a shortcut to `addSubview` 104 | and 'translatesAutoresizingMaskIntoConstraints = false' 105 | 106 | 107 | 108 | ``` 109 | class MyView: UIView { 110 | 111 | let email = UITextField() 112 | let password = UITextField() 113 | let login = UIButton() 114 | 115 | convenience init() { 116 | self.init(frame: CGRect.zero) 117 | 118 | subviews { 119 | email 120 | password 121 | login 122 | } 123 | ... 124 | 125 | } 126 | } 127 | 128 | ``` 129 | 130 | - Returns: Itself to enable nested layouts. 131 | */ 132 | @discardableResult 133 | func subviews(@SubviewsBuilder content: () -> UIView) -> UIView { 134 | let subview = content() 135 | subviews(subview) 136 | return self 137 | } 138 | 139 | /** 140 | Defines the view hierachy for the view. 141 | 142 | Esentially, this is just a shortcut to `addSubview` 143 | and 'translatesAutoresizingMaskIntoConstraints = false' 144 | 145 | 146 | ``` 147 | class MyView: UIView { 148 | 149 | let email = UITextField() 150 | let password = UITextField() 151 | let login = UIButton() 152 | 153 | convenience init() { 154 | self.init(frame: CGRect.zero) 155 | 156 | sv( 157 | email, 158 | password, 159 | login 160 | ) 161 | ... 162 | 163 | } 164 | } 165 | 166 | ``` 167 | 168 | - Returns: Itself to enable nested layouts. 169 | */ 170 | @objc 171 | @available(*, deprecated, renamed: "subviews") 172 | @discardableResult 173 | func sv(_ subViews: [UIView]) -> UIView { 174 | subviews(subViews) 175 | } 176 | 177 | 178 | @discardableResult 179 | @objc 180 | func subviews(_ subViews: [UIView]) -> UIView { 181 | for sv in subViews { 182 | addSubview(sv) 183 | sv.translatesAutoresizingMaskIntoConstraints = false 184 | } 185 | return self 186 | } 187 | } 188 | 189 | public extension UITableViewCell { 190 | 191 | /** 192 | Defines the view hierachy for the view. 193 | 194 | Esentially, this is just a shortcut to `contentView.addSubview` 195 | and 'translatesAutoresizingMaskIntoConstraints = false' 196 | 197 | ``` 198 | class NotificationCell: UITableViewCell { 199 | 200 | var avatar = UIImageView() 201 | var name = UILabel() 202 | var followButton = UIButton() 203 | 204 | required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) } 205 | override init(style: UITableViewCellStyle, reuseIdentifier: String?) { 206 | super.init(style: style, reuseIdentifier: reuseIdentifier) { 207 | 208 | sv( 209 | avatar, 210 | name, 211 | followButton 212 | ) 213 | ... 214 | 215 | } 216 | } 217 | ``` 218 | 219 | - Returns: Itself to enable nested layouts. 220 | */ 221 | @available(*, deprecated, renamed: "subviews") 222 | @discardableResult 223 | override func sv(_ subViews: [UIView]) -> UIView { 224 | contentView.subviews(subViews) 225 | } 226 | 227 | /** 228 | Defines the view hierachy for the view. 229 | 230 | Esentially, this is just a shortcut to `contentView.addSubview` 231 | and 'translatesAutoresizingMaskIntoConstraints = false' 232 | 233 | ``` 234 | class NotificationCell: UITableViewCell { 235 | 236 | var avatar = UIImageView() 237 | var name = UILabel() 238 | var followButton = UIButton() 239 | 240 | required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) } 241 | override init(style: UITableViewCellStyle, reuseIdentifier: String?) { 242 | super.init(style: style, reuseIdentifier: reuseIdentifier) { 243 | 244 | subviews( 245 | avatar, 246 | name, 247 | followButton 248 | ) 249 | ... 250 | 251 | } 252 | } 253 | ``` 254 | 255 | - Returns: Itself to enable nested layouts. 256 | */ 257 | @discardableResult 258 | override func subviews(_ subViews: [UIView]) -> UIView { 259 | contentView.subviews(subViews) 260 | } 261 | } 262 | 263 | public extension UICollectionViewCell { 264 | /** 265 | Defines the view hierachy for the view. 266 | 267 | Esentially, this is just a shortcut to `contentView.addSubview` 268 | and 'translatesAutoresizingMaskIntoConstraints = false' 269 | 270 | ``` 271 | class PhotoCollectionViewCell: UICollectionViewCell { 272 | 273 | var avatar = UIImageView() 274 | var name = UILabel() 275 | var followButton = UIButton() 276 | 277 | 278 | required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } 279 | override init(frame: CGRect) { 280 | super.init(frame: frame) 281 | 282 | subviews( 283 | avatar, 284 | name, 285 | followButton 286 | ) 287 | ... 288 | 289 | } 290 | } 291 | ``` 292 | 293 | - Returns: Itself to enable nested layouts. 294 | */ 295 | @available(*, deprecated, renamed: "subviews") 296 | @discardableResult 297 | override func sv(_ subViews: [UIView]) -> UIView { 298 | contentView.subviews(subViews) 299 | } 300 | 301 | @discardableResult 302 | override func subviews(_ subViews: [UIView]) -> UIView { 303 | contentView.subviews(subViews) 304 | } 305 | } 306 | 307 | 308 | public extension UIStackView { 309 | 310 | @discardableResult 311 | func arrangedSubviews(@SubviewsBuilder content: () -> [UIView]) -> UIView { 312 | arrangedSubviews(content()) 313 | } 314 | 315 | @discardableResult 316 | func arrangedSubviews(@SubviewsBuilder content: () -> UIView) -> UIView { 317 | arrangedSubviews([content()]) 318 | } 319 | 320 | @discardableResult 321 | private func arrangedSubviews(_ subViews: UIView...) -> UIView { 322 | arrangedSubviews(subViews) 323 | } 324 | 325 | @discardableResult 326 | func arrangedSubviews(_ subViews: [UIView]) -> UIView { 327 | subViews.forEach { addArrangedSubview($0) } 328 | return self 329 | } 330 | } 331 | 332 | #endif 333 | -------------------------------------------------------------------------------- /EasyListViewExample/Vendor/Stevia/Stevia+LayoutAnchors.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Stevia+LayoutAnchors.swift 3 | // Stevia 4 | // 5 | // Created by Sacha DSO on 09/10/2017. 6 | // Copyright © 2017 Sacha Durand Saint Omer. All rights reserved. 7 | // 8 | 9 | #if canImport(UIKit) 10 | import UIKit 11 | 12 | @available(iOS 9.0, *) 13 | public struct SteviaLayoutYAxisAnchor { 14 | let anchor: NSLayoutYAxisAnchor 15 | let constant: Double 16 | 17 | init(anchor: NSLayoutYAxisAnchor, constant: Double = 0) { 18 | self.anchor = anchor 19 | self.constant = constant 20 | } 21 | } 22 | 23 | @available(iOS 9.0, *) 24 | public struct SteviaLayoutXAxisAnchor { 25 | let anchor: NSLayoutXAxisAnchor 26 | let constant: Double 27 | 28 | init(anchor: NSLayoutXAxisAnchor, constant: Double = 0) { 29 | self.anchor = anchor 30 | self.constant = constant 31 | } 32 | } 33 | 34 | @available(iOS 9.0, *) 35 | public extension UILayoutGuide { 36 | 37 | var Top: SteviaLayoutYAxisAnchor { 38 | return SteviaLayoutYAxisAnchor(anchor: topAnchor) 39 | } 40 | 41 | var Bottom: SteviaLayoutYAxisAnchor { 42 | return SteviaLayoutYAxisAnchor(anchor: bottomAnchor) 43 | } 44 | 45 | var Left: SteviaLayoutXAxisAnchor { 46 | return SteviaLayoutXAxisAnchor(anchor: leftAnchor) 47 | } 48 | 49 | var Right: SteviaLayoutXAxisAnchor { 50 | return SteviaLayoutXAxisAnchor(anchor: rightAnchor) 51 | } 52 | 53 | var Leading: SteviaLayoutXAxisAnchor { 54 | return SteviaLayoutXAxisAnchor(anchor: leadingAnchor) 55 | } 56 | 57 | var Trailing: SteviaLayoutXAxisAnchor { 58 | return SteviaLayoutXAxisAnchor(anchor: trailingAnchor) 59 | } 60 | 61 | var CenterX: SteviaLayoutXAxisAnchor { 62 | return SteviaLayoutXAxisAnchor(anchor: centerXAnchor) 63 | } 64 | 65 | var CenterY: SteviaLayoutYAxisAnchor { 66 | return SteviaLayoutYAxisAnchor(anchor: centerYAnchor) 67 | } 68 | } 69 | 70 | @available(iOS 9.0, *) 71 | @discardableResult 72 | public func == (left: SteviaAttribute, right: SteviaLayoutYAxisAnchor) -> NSLayoutConstraint { 73 | 74 | var constraint = NSLayoutConstraint() 75 | 76 | if left.attribute == .top { 77 | constraint = left.view.topAnchor.constraint(equalTo: right.anchor, constant: CGFloat(right.constant)) 78 | } 79 | 80 | if left.attribute == .bottom { 81 | constraint = left.view.bottomAnchor.constraint(equalTo: right.anchor, constant: CGFloat(right.constant)) 82 | } 83 | 84 | if left.attribute == .centerY { 85 | constraint = left.view.centerYAnchor.constraint(equalTo: right.anchor, constant: CGFloat(right.constant)) 86 | } 87 | 88 | constraint.isActive = true 89 | return constraint 90 | } 91 | 92 | @available(iOS 9.0, *) 93 | @discardableResult 94 | public func == (left: SteviaAttribute, right: SteviaLayoutXAxisAnchor) -> NSLayoutConstraint { 95 | 96 | var constraint = NSLayoutConstraint() 97 | 98 | if left.attribute == .left { 99 | constraint = left.view.leftAnchor.constraint(equalTo: right.anchor, constant: CGFloat(right.constant)) 100 | } 101 | 102 | if left.attribute == .right { 103 | constraint = left.view.rightAnchor.constraint(equalTo: right.anchor, constant: CGFloat(right.constant)) 104 | } 105 | 106 | if left.attribute == .leading { 107 | constraint = left.view.leadingAnchor.constraint(equalTo: right.anchor, constant: CGFloat(right.constant)) 108 | } 109 | 110 | if left.attribute == .trailing { 111 | constraint = left.view.trailingAnchor.constraint(equalTo: right.anchor, constant: CGFloat(right.constant)) 112 | } 113 | 114 | if left.attribute == .centerX { 115 | constraint = left.view.centerXAnchor.constraint(equalTo: right.anchor, constant: CGFloat(right.constant)) 116 | } 117 | 118 | constraint.isActive = true 119 | return constraint 120 | } 121 | 122 | // SteviaLayoutYAxisAnchor 123 | 124 | @available(iOS 9.0, *) 125 | @discardableResult 126 | public func + (left: SteviaLayoutYAxisAnchor, right: Double) -> SteviaLayoutYAxisAnchor { 127 | return SteviaLayoutYAxisAnchor(anchor: left.anchor, constant: right) 128 | } 129 | 130 | @available(iOS 9.0, *) 131 | @discardableResult 132 | public func - (left: SteviaLayoutYAxisAnchor, right: Double) -> SteviaLayoutYAxisAnchor { 133 | return SteviaLayoutYAxisAnchor(anchor: left.anchor, constant: -right) 134 | } 135 | 136 | @available(iOS 9.0, *) 137 | @discardableResult 138 | public func + (left: SteviaLayoutXAxisAnchor, right: Double) -> SteviaLayoutXAxisAnchor { 139 | return SteviaLayoutXAxisAnchor(anchor: left.anchor, constant: right) 140 | } 141 | 142 | @available(iOS 9.0, *) 143 | @discardableResult 144 | public func - (left: SteviaLayoutXAxisAnchor, right: Double) -> SteviaLayoutXAxisAnchor { 145 | return SteviaLayoutXAxisAnchor(anchor: left.anchor, constant: -right) 146 | } 147 | 148 | // UILayoutSupport 149 | 150 | @available(iOS 9.0, *) 151 | public extension UILayoutSupport { 152 | 153 | var Top: SteviaLayoutYAxisAnchor { 154 | return SteviaLayoutYAxisAnchor(anchor: topAnchor) 155 | } 156 | 157 | var Bottom: SteviaLayoutYAxisAnchor { 158 | return SteviaLayoutYAxisAnchor(anchor: bottomAnchor) 159 | } 160 | } 161 | #endif 162 | -------------------------------------------------------------------------------- /EasyListViewExample/Vendor/Stevia/Stevia+Notifications.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Stevia+Notifications.swift 3 | // LoginStevia 4 | // 5 | // Created by Sacha Durand Saint Omer on 12/10/15. 6 | // Copyright © 2015 Sacha Durand Saint Omer. All rights reserved. 7 | // 8 | 9 | #if canImport(UIKit) 10 | import UIKit 11 | 12 | public extension NSObject { 13 | 14 | func on(_ event: String, _ callback:@escaping () -> Void) { 15 | _ = NotificationCenter.default.addObserver(forName: NSNotification.Name(rawValue: event), 16 | object: nil, 17 | queue: nil) { _ in 18 | callback() 19 | } 20 | } 21 | } 22 | #endif 23 | -------------------------------------------------------------------------------- /EasyListViewExample/Vendor/Stevia/Stevia+Operators.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Stevia+Operators.swift 3 | // Stevia 4 | // 5 | // Created by Sacha Durand Saint Omer on 10/02/16. 6 | // Copyright © 2016 Sacha Durand Saint Omer. All rights reserved. 7 | // 8 | 9 | #if canImport(UIKit) 10 | import UIKit 11 | 12 | prefix operator | 13 | @discardableResult 14 | public prefix func | (p: UIView) -> UIView { 15 | p.leading(0) 16 | } 17 | 18 | postfix operator | 19 | @discardableResult 20 | public postfix func | (p: UIView) -> UIView { 21 | p.trailing(0) 22 | } 23 | 24 | infix operator ~ : HeightPrecedence 25 | 26 | precedencegroup HeightPrecedence { 27 | lowerThan: AdditionPrecedence 28 | } 29 | 30 | @discardableResult 31 | public func ~ (left: UIView, right: Double) -> UIView { 32 | left.height(right) 33 | } 34 | 35 | @discardableResult 36 | public func ~ (left: UIView, right: CGFloat) -> UIView { 37 | left ~ Double(right) 38 | } 39 | 40 | @discardableResult 41 | public func ~ (left: UIView, right: Int) -> UIView { 42 | left ~ Double(right) 43 | } 44 | 45 | @discardableResult 46 | public func ~ (left: UIView, right: SteviaPercentage) -> UIView { 47 | left.height(right) 48 | } 49 | 50 | @discardableResult 51 | public func ~ (left: UIView, right: SteviaFlexibleMargin) -> UIView { 52 | left.height(right) 53 | } 54 | 55 | @discardableResult 56 | public func ~ (left: [UIView], right: Double) -> [UIView] { 57 | for l in left { l.height(right) } 58 | return left 59 | } 60 | 61 | @discardableResult 62 | public func ~ (left: [UIView], right: CGFloat) -> [UIView] { 63 | left ~ Double(right) 64 | } 65 | 66 | @discardableResult 67 | public func ~ (left: [UIView], right: Int) -> [UIView] { 68 | left ~ Double(right) 69 | } 70 | 71 | @discardableResult 72 | public func ~ (left: [UIView], right: SteviaFlexibleMargin) -> [UIView] { 73 | for l in left { l.height(right) } 74 | return left 75 | } 76 | 77 | prefix operator |- 78 | @discardableResult 79 | public prefix func |- (p: Double) -> SideConstraint { 80 | var s = SideConstraint() 81 | s.constant = p 82 | return s 83 | } 84 | 85 | @discardableResult 86 | public prefix func |- (p: CGFloat) -> SideConstraint { 87 | |-Double(p) 88 | } 89 | 90 | @discardableResult 91 | public prefix func |- (p: Int) -> SideConstraint { 92 | |-Double(p) 93 | } 94 | 95 | @discardableResult 96 | public prefix func |- (v: UIView) -> UIView { 97 | v.leading(8) 98 | } 99 | 100 | postfix operator -| 101 | @discardableResult 102 | public postfix func -| (p: Double) -> SideConstraint { 103 | var s = SideConstraint() 104 | s.constant = p 105 | return s 106 | } 107 | 108 | @discardableResult 109 | public postfix func -| (p: CGFloat) -> SideConstraint { 110 | Double(p)-| 111 | } 112 | 113 | @discardableResult 114 | public postfix func -| (p: Int) -> SideConstraint { 115 | Double(p)-| 116 | } 117 | 118 | @discardableResult 119 | public postfix func -| (v: UIView) -> UIView { 120 | v.trailing(8) 121 | } 122 | 123 | public struct SideConstraint { 124 | var constant: Double! 125 | } 126 | 127 | public struct PartialConstraint { 128 | var view1: UIView! 129 | var constant: Double! 130 | var views: [UIView]? 131 | } 132 | 133 | @discardableResult 134 | public func - (left: UIView, right: Double) -> PartialConstraint { 135 | var p = PartialConstraint() 136 | p.view1 = left 137 | p.constant = right 138 | return p 139 | } 140 | 141 | @discardableResult 142 | public func - (left: UIView, right: CGFloat) -> PartialConstraint { 143 | left-Double(right) 144 | } 145 | 146 | @discardableResult 147 | public func - (left: UIView, right: Int) -> PartialConstraint { 148 | left-Double(right) 149 | } 150 | 151 | // Side Constraints 152 | 153 | @discardableResult 154 | public func - (left: SideConstraint, right: UIView) -> UIView { 155 | if let spv = right.superview { 156 | let c = constraint(item: right, attribute: .leading, 157 | toItem: spv, attribute: .leading, 158 | constant: left.constant) 159 | spv.addConstraint(c) 160 | } 161 | return right 162 | } 163 | 164 | @discardableResult 165 | public func - (left: [UIView], right: SideConstraint) -> [UIView] { 166 | let lastView = left[left.count-1] 167 | if let spv = lastView.superview { 168 | let c = constraint(item: lastView, attribute: .trailing, 169 | toItem: spv, attribute: .trailing, 170 | constant: -right.constant) 171 | spv.addConstraint(c) 172 | } 173 | return left 174 | } 175 | 176 | @discardableResult 177 | public func - (left: UIView, right: SideConstraint) -> UIView { 178 | if let spv = left.superview { 179 | let c = constraint(item: left, attribute: .trailing, 180 | toItem: spv, attribute: .trailing, 181 | constant: -right.constant) 182 | spv.addConstraint(c) 183 | } 184 | return left 185 | } 186 | 187 | @discardableResult 188 | public func - (left: PartialConstraint, right: UIView) -> [UIView] { 189 | if let views = left.views { 190 | if let spv = right.superview { 191 | let lastView = views[views.count-1] 192 | let c = constraint(item: lastView, attribute: .trailing, 193 | toItem: right, attribute: .leading, 194 | constant: -left.constant) 195 | spv.addConstraint(c) 196 | } 197 | 198 | return views + [right] 199 | } else { 200 | // were at the end?? nooope?/? 201 | if let spv = right.superview { 202 | let c = constraint(item: left.view1, attribute: .trailing, 203 | toItem: right, attribute: .leading, 204 | constant: -left.constant) 205 | spv.addConstraint(c) 206 | } 207 | return [left.view1, right] 208 | } 209 | } 210 | 211 | @discardableResult 212 | public func - (left: UIView, right: UIView) -> [UIView] { 213 | if let spv = left.superview { 214 | let c = constraint(item: right, attribute: .leading, 215 | toItem: left, attribute: .trailing, 216 | constant: 8) 217 | spv.addConstraint(c) 218 | } 219 | return [left, right] 220 | } 221 | 222 | @discardableResult 223 | public func - (left: [UIView], right: Double) -> PartialConstraint { 224 | var p = PartialConstraint() 225 | p.constant = right 226 | p.views = left 227 | return p 228 | } 229 | 230 | @discardableResult 231 | public func - (left: [UIView], right: CGFloat) -> PartialConstraint { 232 | left-Double(right) 233 | } 234 | 235 | @discardableResult 236 | public func - (left: [UIView], right: Int) -> PartialConstraint { 237 | left-Double(right) 238 | } 239 | 240 | 241 | @discardableResult 242 | public func - (left: [UIView], right: UIView) -> [UIView] { 243 | let lastView = left[left.count-1] 244 | if let spv = lastView.superview { 245 | let c = constraint(item: lastView, attribute: .trailing, 246 | toItem: right, attribute: .leading, 247 | constant: -8) 248 | spv.addConstraint(c) 249 | } 250 | return left + [right] 251 | } 252 | 253 | //// Test space in Horizointal layout "" 254 | public struct Space { 255 | var previousViews: [UIView]! 256 | } 257 | 258 | @discardableResult 259 | public func - (left: UIView, right: String) -> Space { 260 | Space(previousViews: [left]) 261 | } 262 | 263 | @discardableResult 264 | public func - (left: [UIView], right: String) -> Space { 265 | Space(previousViews: left) 266 | } 267 | 268 | @discardableResult 269 | public func - (left: Space, right: UIView) -> [UIView] { 270 | var va = left.previousViews 271 | va?.append(right) 272 | return va! 273 | } 274 | #endif 275 | -------------------------------------------------------------------------------- /EasyListViewExample/Vendor/Stevia/Stevia+Percentage.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Stevia+Percentage.swift 3 | // Stevia 4 | // 5 | // Created by Sacha Durand Saint Omer on 21/01/2017. 6 | // Copyright © 2017 Sacha Durand Saint Omer. All rights reserved. 7 | // 8 | 9 | #if canImport(UIKit) 10 | import UIKit 11 | 12 | public struct SteviaPercentage { 13 | let value: Double 14 | } 15 | 16 | postfix operator % 17 | public postfix func % (v: Double) -> SteviaPercentage { 18 | SteviaPercentage(value: Double(v)) 19 | } 20 | 21 | public postfix func % (v: CGFloat) -> SteviaPercentage { 22 | Double(v)% 23 | } 24 | 25 | public postfix func % (v: Int) -> SteviaPercentage { 26 | Double(v)% 27 | } 28 | 29 | public extension UIView { 30 | 31 | /** 32 | Adds an Autolayout constraint for sizing the view. 33 | 34 | ``` 35 | image.size(100) 36 | image.size(100%) 37 | 38 | // is equivalent to 39 | 40 | image.width(100).height(100) 41 | ``` 42 | 43 | - Returns: Itself, enabling chaining, 44 | 45 | */ 46 | @discardableResult 47 | func size(_ p: SteviaPercentage) -> Self { 48 | width(p) 49 | height(p) 50 | return self 51 | } 52 | 53 | /** 54 | Adds an Autolayout constraint for setting the view's width. 55 | 56 | ``` 57 | image.width(100) 58 | image.width(<=100) 59 | image.width(>=100) 60 | image.width(100%) 61 | ``` 62 | 63 | - Returns: Itself, enabling chaining, 64 | 65 | */ 66 | @discardableResult 67 | func width(_ p: SteviaPercentage) -> Self { 68 | if let spv = superview { 69 | Width == p.value % spv.Width 70 | } 71 | return self 72 | } 73 | 74 | /** 75 | Adds an Autolayout constraint for setting the view's height. 76 | 77 | ``` 78 | image.height(100) 79 | 80 | // is equivalent to 81 | 82 | image ~ 100 83 | 84 | // Flexible margins 85 | image.height(<=100) 86 | image.height(>=100) 87 | image.height(100%) 88 | ``` 89 | 90 | - Returns: Itself, enabling chaining, 91 | 92 | */ 93 | @discardableResult 94 | func height(_ p: SteviaPercentage) -> Self { 95 | if let spv = superview { 96 | Height == p.value % spv.Height 97 | } 98 | return self 99 | } 100 | 101 | /** Sets the top margin for a view. 102 | 103 | Example Usage : 104 | 105 | label.top(20) 106 | label.top(<=20) 107 | label.top(>=20) 108 | label.top(20%) 109 | 110 | - Returns: Itself for chaining purposes 111 | */ 112 | @discardableResult 113 | func top(_ p: SteviaPercentage) -> Self { 114 | if let spv = superview { 115 | Top == p.value % spv.Bottom 116 | } 117 | return self 118 | } 119 | 120 | /** Sets the left margin for a view. 121 | 122 | Example Usage : 123 | 124 | label.left(20) 125 | label.left(<=20) 126 | label.left(>=20) 127 | label.left(20%) 128 | 129 | - Returns: Itself for chaining purposes 130 | */ 131 | @discardableResult 132 | func left(_ p: SteviaPercentage) -> Self { 133 | if let spv = superview { 134 | Left == p.value % spv.Right 135 | } 136 | return self 137 | } 138 | 139 | /** Sets the right margin for a view. 140 | 141 | Example Usage : 142 | 143 | label.right(20) 144 | label.right(<=20) 145 | label.right(>=20) 146 | label.right(20%) 147 | 148 | - Returns: Itself for chaining purposes 149 | */ 150 | @discardableResult 151 | func right(_ p: SteviaPercentage) -> Self { 152 | if let spv = superview { 153 | if p.value == 100 { 154 | Right == spv.Left 155 | } else { 156 | Right == (100 - p.value) % spv.Right 157 | } 158 | } 159 | return self 160 | } 161 | 162 | /** Sets the bottom margin for a view. 163 | 164 | Example Usage : 165 | 166 | label.bottom(20) 167 | label.bottom(<=20) 168 | label.bottom(>=20) 169 | label.bottom(20%) 170 | 171 | - Returns: Itself for chaining purposes 172 | */ 173 | @discardableResult 174 | func bottom(_ p: SteviaPercentage) -> Self { 175 | if let spv = superview { 176 | if p.value == 100 { 177 | Bottom == spv.Top 178 | } else { 179 | Bottom == (100 - p.value) % spv.Bottom 180 | } 181 | } 182 | return self 183 | } 184 | 185 | /** Sets the leading margin for a view. 186 | 187 | Example Usage : 188 | 189 | label.leading(20) 190 | label.leading(<=20) 191 | label.leading(>=20) 192 | label.leading(20%) 193 | 194 | - Returns: itself for chaining purposes 195 | */ 196 | @discardableResult 197 | func leading(_ p: SteviaPercentage) -> UIView { 198 | // Percent based (multipliers) with leading or trailing attributes 199 | // are not available so we introduce an intermediary layout guide. 200 | // |-[layoutGuide(== x % width)-[view] 201 | // RTL version: [view]-[layoutGuide(== x % width)-| 202 | if let spv = superview { 203 | let lg = UILayoutGuide() 204 | spv.addLayoutGuide(lg) 205 | let constraints = [ 206 | lg.widthAnchor.constraint(equalTo: spv.widthAnchor, multiplier: CGFloat(p.value / 100.0)), 207 | lg.leadingAnchor.constraint(equalTo: spv.leadingAnchor), 208 | leadingAnchor.constraint(equalTo: lg.trailingAnchor) 209 | ] 210 | constraints.forEach { $0.isActive = true } 211 | } 212 | return self 213 | } 214 | 215 | /** Sets the trailing margin for a view. 216 | 217 | Example Usage : 218 | 219 | label.trailing(20) 220 | label.trailing(<=20) 221 | label.trailing(>=20) 222 | label.trailing(20%) 223 | 224 | - Returns: itself for chaining purposes 225 | */ 226 | @discardableResult 227 | func trailing(_ p: SteviaPercentage) -> UIView { 228 | 229 | // Percent based (multipliers) with leading or trailing attributes 230 | // are not available so we introduce an intermediary layout guide. 231 | // |-[layoutGuide(== x % width)-[view] 232 | // RTL version: [view]-[layoutGuide(== x % width)-| 233 | if let spv = superview { 234 | let lg = UILayoutGuide() 235 | spv.addLayoutGuide(lg) 236 | let constraints = [ 237 | lg.widthAnchor.constraint(equalTo: spv.widthAnchor, multiplier: CGFloat(p.value / Double(100))), 238 | lg.trailingAnchor.constraint(equalTo: spv.trailingAnchor), 239 | trailingAnchor.constraint(equalTo: lg.leadingAnchor) 240 | ] 241 | constraints.forEach { $0.isActive = true } 242 | } 243 | return self 244 | } 245 | } 246 | #endif 247 | -------------------------------------------------------------------------------- /EasyListViewExample/Vendor/Stevia/Stevia+Stacks.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Stevia+Stacks.swift 3 | // Stevia 4 | // 5 | // Created by Sacha Durand Saint Omer on 10/02/16. 6 | // Copyright © 2016 Sacha Durand Saint Omer. All rights reserved. 7 | // 8 | 9 | #if canImport(UIKit) 10 | import UIKit 11 | 12 | //enum SteviaLayoutItemType { 13 | // 14 | //} 15 | 16 | public protocol SteviaLayoutItem { 17 | var any: Any {get} 18 | } 19 | 20 | extension SteviaLayoutItem { 21 | public var any: Any { self } 22 | } 23 | extension UIView: SteviaLayoutItem {} 24 | extension Int: SteviaLayoutItem {} 25 | extension Double: SteviaLayoutItem {} 26 | extension CGFloat: SteviaLayoutItem {} 27 | extension String: SteviaLayoutItem {} 28 | 29 | extension FlexibleSpace: SteviaLayoutItem {} 30 | extension SteviaFlexibleMargin: SteviaLayoutItem {} 31 | extension SteviaPercentage: SteviaLayoutItem {} 32 | extension Array: SteviaLayoutItem where Element: UIView {} 33 | 34 | public struct FlexibleSpace { 35 | public init() {} 36 | } 37 | 38 | @_functionBuilder public struct SteviaLayoutBuilder { 39 | public static func buildBlock(_ content: SteviaLayoutItem...) -> [SteviaLayoutItem] { 40 | return content 41 | } 42 | } 43 | 44 | public extension UIView { 45 | @discardableResult 46 | func layout(@SteviaLayoutBuilder content: () -> [SteviaLayoutItem]) -> UIView { 47 | let subviews = content() 48 | let anys = subviews.map { $0.any } 49 | layout(anys) 50 | return self 51 | } 52 | } 53 | 54 | public extension UIView { 55 | 56 | /** 57 | 58 | Lays out the views on both axis. 59 | 60 | Note that this is not needed for Horizontal only layouts. 61 | 62 | `layout` is primarily for laying out views vertically but horizontal statements 63 | are supported, making it perfect for describing a layout in one single statement. 64 | 65 | ``` 66 | layout( 67 | 100, 68 | |-email-| ~ 80, 69 | 8, 70 | |-password-forgot-| ~ 80, 71 | >=20, 72 | |login| ~ 80, 73 | 0 74 | ) 75 | ``` 76 | */ 77 | // @available(*, deprecated, message: "Use Layout { } function builder instead.") 78 | @discardableResult 79 | func layout(_ objects: Any...) -> [UIView] { 80 | return layout(objects) 81 | } 82 | 83 | @discardableResult 84 | func layout(_ objects: [Any]) -> [UIView] { 85 | var previousMargin: Double? 86 | var previousFlexibleMargin: SteviaFlexibleMargin? 87 | var previousPercentMargin: SteviaPercentage? 88 | 89 | for (i, o) in objects.enumerated() { 90 | 91 | func viewLogic(_ v: UIView) { 92 | if let pm = previousMargin { 93 | if i == 1 { 94 | v.top(pm) // only if first view 95 | } else { 96 | if let vx = objects[i-2] as? UIView { 97 | vx.stackV(m: pm, v: v) 98 | } else if let va = objects[i-2] as? [UIView] { 99 | va.first!.stackV(m: pm, v: v) 100 | } 101 | } 102 | previousMargin = nil 103 | } else if let pfm = previousFlexibleMargin { 104 | if i == 1 { 105 | v.top(pfm) // only if first view 106 | } else { 107 | if let vx = objects[i-2] as? UIView { 108 | addConstraint( 109 | item: v, attribute: .top, 110 | relatedBy: pfm.relation, 111 | toItem: vx, attribute: .bottom, 112 | multiplier: 1, constant: pfm.points 113 | ) 114 | } else if let va = objects[i-2] as? [UIView] { 115 | addConstraint( 116 | item: v, attribute: .top, 117 | relatedBy: pfm.relation, 118 | toItem: va.first!, attribute: .bottom, 119 | multiplier: 1, constant: pfm.points 120 | ) 121 | } 122 | } 123 | previousFlexibleMargin = nil 124 | } else if let ppm = previousPercentMargin { 125 | if i == 1 { 126 | v.top(ppm) // only if first view 127 | } else { 128 | if let vx = objects[i-2] as? UIView { 129 | // Add layout guide to suport %-based spaces. 130 | let percent = ppm.value / 100 131 | 132 | let lg = UILayoutGuide() 133 | addLayoutGuide(lg) 134 | NSLayoutConstraint.activate([ 135 | lg.topAnchor.constraint(equalTo: vx.bottomAnchor), 136 | lg.heightAnchor.constraint(equalTo: heightAnchor, multiplier: CGFloat(percent)), 137 | v.topAnchor.constraint(equalTo: lg.bottomAnchor) 138 | ]) 139 | } 140 | } 141 | previousPercentMargin = nil 142 | } else { 143 | tryStackViewVerticallyWithPreviousView(v, index: i, objects: objects) 144 | } 145 | } 146 | 147 | switch o { 148 | case let v as UIView: 149 | viewLogic(v) 150 | case is Int, is Double, is CGFloat: 151 | let m = doubleMarginFromObject(o) 152 | previousMargin = m // Store margin for next pass 153 | if i != 0 && i == (objects.count - 1) { 154 | //Last Margin, Bottom 155 | if let previousView = objects[i-1] as? UIView { 156 | previousView.bottom(m) 157 | } else if let va = objects[i-1] as? [UIView] { 158 | va.first!.bottom(m) 159 | } 160 | } 161 | case let fm as SteviaFlexibleMargin: 162 | previousFlexibleMargin = fm // Store margin for next pass 163 | if i != 0 && i == (objects.count - 1) { 164 | //Last Margin, Bottom 165 | if let previousView = objects[i-1] as? UIView { 166 | previousView.bottom(fm) 167 | } else if let va = objects[i-1] as? [UIView] { 168 | va.first!.bottom(fm) 169 | } 170 | } 171 | case let pm as SteviaPercentage: 172 | previousPercentMargin = pm // Store margin for next pass 173 | if i != 0 && i == (objects.count - 1) { 174 | //Last Margin, Bottom 175 | if let previousView = objects[i-1] as? UIView { 176 | previousView.bottom(pm) 177 | } else if let va = objects[i-1] as? [UIView] { 178 | va.first!.bottom(pm) 179 | } 180 | } 181 | case _ as String:() //Do nothin' ! 182 | case let a as [UIView]: 183 | align(horizontally: a) 184 | let v = a.first! 185 | viewLogic(v) 186 | default: () 187 | } 188 | } 189 | return objects.map {$0 as? UIView }.compactMap {$0} 190 | } 191 | 192 | fileprivate func doubleMarginFromObject(_ o: Any) -> Double { 193 | var m: Double = 0 194 | if let i = o as? Int { 195 | m = Double(i) 196 | } else if let d = o as? Double { 197 | m = d 198 | } else if let cg = o as? CGFloat { 199 | m = Double(cg) 200 | } 201 | return m 202 | } 203 | 204 | fileprivate func tryStackViewVerticallyWithPreviousView(_ view: UIView, 205 | index: Int, objects: [Any]) { 206 | if let pv = previousViewFromIndex(index, objects: objects) { 207 | pv.stackV(v: view) 208 | } 209 | } 210 | 211 | fileprivate func previousViewFromIndex(_ index: Int, objects: [Any]) -> UIView? { 212 | if index != 0 { 213 | if let previousView = objects[index-1] as? UIView { 214 | return previousView 215 | } 216 | } 217 | return nil 218 | } 219 | 220 | @discardableResult 221 | fileprivate func stackV(m points: Double = 0, v: UIView) -> UIView { 222 | return stack(.vertical, points: points, v: v) 223 | } 224 | 225 | fileprivate func stack(_ axis: NSLayoutConstraint.Axis, 226 | points: Double = 0, v: UIView) -> UIView { 227 | let a: NSLayoutConstraint.Attribute = axis == .vertical ? .top : .left 228 | let b: NSLayoutConstraint.Attribute = axis == .vertical ? .bottom : .right 229 | if let spv = superview { 230 | let c = constraint(item: v, attribute: a, toItem: self, attribute: b, constant: points) 231 | spv.addConstraint(c) 232 | } 233 | return v 234 | } 235 | } 236 | #endif 237 | -------------------------------------------------------------------------------- /EasyListViewExample/Vendor/Stevia/Stevia+Style.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Stevia+Style.swift 3 | // LoginStevia 4 | // 5 | // Created by Sacha Durand Saint Omer on 01/10/15. 6 | // Copyright © 2015 Sacha Durand Saint Omer. All rights reserved. 7 | // 8 | 9 | #if canImport(UIKit) 10 | import UIKit 11 | 12 | public extension UIAppearance { 13 | 14 | /** Applies a styling block on an element. 15 | 16 | Example Usage: 17 | 18 | ``` 19 | button.style { b in 20 | b.A = X 21 | b.B = Y 22 | b.C = Z 23 | } 24 | ``` 25 | 26 | Handy for reusing styles : 27 | ``` 28 | button.style(buttonStyle) 29 | 30 | // later 31 | func buttonStyle(b: UIButton) { 32 | ..styling code 33 | } 34 | ``` 35 | 36 | - Returns: Itself for chaining purposes 37 | 38 | */ 39 | @discardableResult 40 | func style(_ styleClosure: (Self) -> Void) -> Self { 41 | styleClosure(self) 42 | return self 43 | } 44 | } 45 | #endif 46 | -------------------------------------------------------------------------------- /Kapture1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moliya/EasyListView/de589ce71c695759034df8f28d8b7b4168b4168e/Kapture1.gif -------------------------------------------------------------------------------- /Kapture2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moliya/EasyListView/de589ce71c695759034df8f28d8b7b4168b4168e/Kapture2.gif -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 moliya 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 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.0 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "EasyListView", 8 | platforms: [ 9 | .iOS(.v9) 10 | ], 11 | products: [ 12 | .library(name: "EasyListView", targets: ["EasyListView"]) 13 | ], 14 | dependencies: [ 15 | .package(url: "https://github.com/moliya/EasyCompatible.git", from: "1.0.0") 16 | ], 17 | targets: [ 18 | .target(name: "EasyListView", dependencies: ["EasyCompatible"], path: "Sources") 19 | ], 20 | swiftLanguageVersions: [.v5] 21 | ) 22 | -------------------------------------------------------------------------------- /Podfile: -------------------------------------------------------------------------------- 1 | source 'https://cdn.cocoapods.org/' 2 | use_frameworks! 3 | platform :ios, '9.0' 4 | 5 | target 'EasyListViewExample' do 6 | pod 'EasyListView', :path => './' 7 | end 8 | -------------------------------------------------------------------------------- /Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - EasyCompatible (1.0) 3 | - EasyListView (1.3.0): 4 | - EasyCompatible 5 | 6 | DEPENDENCIES: 7 | - EasyListView (from `./`) 8 | 9 | SPEC REPOS: 10 | trunk: 11 | - EasyCompatible 12 | 13 | EXTERNAL SOURCES: 14 | EasyListView: 15 | :path: "./" 16 | 17 | SPEC CHECKSUMS: 18 | EasyCompatible: 571e10cf69d018b820f65cd6b99f76573313db6e 19 | EasyListView: ac1b3dcd19ef3fb5b2ea48b74b91a8171cc723e8 20 | 21 | PODFILE CHECKSUM: 85d9c6237193f3c7e89929aa9b3c1282aad65d38 22 | 23 | COCOAPODS: 1.11.3 24 | -------------------------------------------------------------------------------- /Pods/EasyCompatible/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 moliya 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 | -------------------------------------------------------------------------------- /Pods/EasyCompatible/README.md: -------------------------------------------------------------------------------- 1 | # EasyCompatible 2 | 一个基础依赖 3 | -------------------------------------------------------------------------------- /Pods/EasyCompatible/Sources/EasyCompatible.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EasyCompatible.swift 3 | // EasyCompatible 4 | // 5 | // Created by carefree on 2022/3/25. 6 | // Copyright © 2019 Carefree. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | // MARK: - 命名空间 12 | public struct EasyExtension { 13 | public let base: Base 14 | public init(_ base: Base) { 15 | self.base = base 16 | } 17 | } 18 | 19 | public protocol EasyCompatible: AnyObject { } 20 | 21 | public protocol EasyCompatibleValue {} 22 | 23 | extension EasyCompatible { 24 | public var easy: EasyExtension { 25 | get { return EasyExtension(self) } 26 | set { } 27 | } 28 | } 29 | 30 | extension EasyCompatibleValue { 31 | public var easy: EasyExtension { 32 | get { return EasyExtension(self) } 33 | set { } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Pods/Local Podspecs/EasyListView.podspec.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "EasyListView", 3 | "version": "1.3.0", 4 | "summary": "快速搭建静态及可重用列表", 5 | "homepage": "https://github.com/moliya/EasyListView", 6 | "license": "MIT", 7 | "authors": { 8 | "Carefree": "946715806@qq.com" 9 | }, 10 | "source": { 11 | "git": "https://github.com/moliya/EasyListView.git", 12 | "tag": "1.3.0" 13 | }, 14 | "requires_arc": true, 15 | "platforms": { 16 | "ios": "9.0" 17 | }, 18 | "swift_versions": "5.0", 19 | "dependencies": { 20 | "EasyCompatible": [ 21 | 22 | ] 23 | }, 24 | "source_files": "Sources/**/*", 25 | "swift_version": "5.0" 26 | } 27 | -------------------------------------------------------------------------------- /Pods/Manifest.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - EasyCompatible (1.0) 3 | - EasyListView (1.3.0): 4 | - EasyCompatible 5 | 6 | DEPENDENCIES: 7 | - EasyListView (from `./`) 8 | 9 | SPEC REPOS: 10 | trunk: 11 | - EasyCompatible 12 | 13 | EXTERNAL SOURCES: 14 | EasyListView: 15 | :path: "./" 16 | 17 | SPEC CHECKSUMS: 18 | EasyCompatible: 571e10cf69d018b820f65cd6b99f76573313db6e 19 | EasyListView: ac1b3dcd19ef3fb5b2ea48b74b91a8171cc723e8 20 | 21 | PODFILE CHECKSUM: 85d9c6237193f3c7e89929aa9b3c1282aad65d38 22 | 23 | COCOAPODS: 1.11.3 24 | -------------------------------------------------------------------------------- /Pods/Target Support Files/EasyCompatible/EasyCompatible-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | ${PRODUCT_BUNDLE_IDENTIFIER} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | ${PRODUCT_NAME} 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | ${CURRENT_PROJECT_VERSION} 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Pods/Target Support Files/EasyCompatible/EasyCompatible-dummy.m: -------------------------------------------------------------------------------- 1 | #import 2 | @interface PodsDummy_EasyCompatible : NSObject 3 | @end 4 | @implementation PodsDummy_EasyCompatible 5 | @end 6 | -------------------------------------------------------------------------------- /Pods/Target Support Files/EasyCompatible/EasyCompatible-prefix.pch: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | -------------------------------------------------------------------------------- /Pods/Target Support Files/EasyCompatible/EasyCompatible-umbrella.h: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | 14 | FOUNDATION_EXPORT double EasyCompatibleVersionNumber; 15 | FOUNDATION_EXPORT const unsigned char EasyCompatibleVersionString[]; 16 | 17 | -------------------------------------------------------------------------------- /Pods/Target Support Files/EasyCompatible/EasyCompatible.debug.xcconfig: -------------------------------------------------------------------------------- 1 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO 2 | CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/EasyCompatible 3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 4 | LIBRARY_SEARCH_PATHS = $(inherited) "${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift 5 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS 6 | PODS_BUILD_DIR = ${BUILD_DIR} 7 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 8 | PODS_ROOT = ${SRCROOT} 9 | PODS_TARGET_SRCROOT = ${PODS_ROOT}/EasyCompatible 10 | PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates 11 | PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} 12 | SKIP_INSTALL = YES 13 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES 14 | -------------------------------------------------------------------------------- /Pods/Target Support Files/EasyCompatible/EasyCompatible.modulemap: -------------------------------------------------------------------------------- 1 | framework module EasyCompatible { 2 | umbrella header "EasyCompatible-umbrella.h" 3 | 4 | export * 5 | module * { export * } 6 | } 7 | -------------------------------------------------------------------------------- /Pods/Target Support Files/EasyCompatible/EasyCompatible.release.xcconfig: -------------------------------------------------------------------------------- 1 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO 2 | CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/EasyCompatible 3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 4 | LIBRARY_SEARCH_PATHS = $(inherited) "${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift 5 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS 6 | PODS_BUILD_DIR = ${BUILD_DIR} 7 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 8 | PODS_ROOT = ${SRCROOT} 9 | PODS_TARGET_SRCROOT = ${PODS_ROOT}/EasyCompatible 10 | PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates 11 | PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} 12 | SKIP_INSTALL = YES 13 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES 14 | -------------------------------------------------------------------------------- /Pods/Target Support Files/EasyListView/EasyListView-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | ${PRODUCT_BUNDLE_IDENTIFIER} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | ${PRODUCT_NAME} 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.3.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | ${CURRENT_PROJECT_VERSION} 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Pods/Target Support Files/EasyListView/EasyListView-dummy.m: -------------------------------------------------------------------------------- 1 | #import 2 | @interface PodsDummy_EasyListView : NSObject 3 | @end 4 | @implementation PodsDummy_EasyListView 5 | @end 6 | -------------------------------------------------------------------------------- /Pods/Target Support Files/EasyListView/EasyListView-prefix.pch: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | -------------------------------------------------------------------------------- /Pods/Target Support Files/EasyListView/EasyListView-umbrella.h: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | 14 | FOUNDATION_EXPORT double EasyListViewVersionNumber; 15 | FOUNDATION_EXPORT const unsigned char EasyListViewVersionString[]; 16 | 17 | -------------------------------------------------------------------------------- /Pods/Target Support Files/EasyListView/EasyListView.debug.xcconfig: -------------------------------------------------------------------------------- 1 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO 2 | CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/EasyListView 3 | FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/EasyCompatible" 4 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 5 | LIBRARY_SEARCH_PATHS = $(inherited) "${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift 6 | OTHER_LDFLAGS = $(inherited) -framework "EasyCompatible" 7 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS 8 | PODS_BUILD_DIR = ${BUILD_DIR} 9 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 10 | PODS_ROOT = ${SRCROOT} 11 | PODS_TARGET_SRCROOT = ${PODS_ROOT}/.. 12 | PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates 13 | PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} 14 | SKIP_INSTALL = YES 15 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES 16 | -------------------------------------------------------------------------------- /Pods/Target Support Files/EasyListView/EasyListView.modulemap: -------------------------------------------------------------------------------- 1 | framework module EasyListView { 2 | umbrella header "EasyListView-umbrella.h" 3 | 4 | export * 5 | module * { export * } 6 | } 7 | -------------------------------------------------------------------------------- /Pods/Target Support Files/EasyListView/EasyListView.release.xcconfig: -------------------------------------------------------------------------------- 1 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO 2 | CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/EasyListView 3 | FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/EasyCompatible" 4 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 5 | LIBRARY_SEARCH_PATHS = $(inherited) "${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift 6 | OTHER_LDFLAGS = $(inherited) -framework "EasyCompatible" 7 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS 8 | PODS_BUILD_DIR = ${BUILD_DIR} 9 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 10 | PODS_ROOT = ${SRCROOT} 11 | PODS_TARGET_SRCROOT = ${PODS_ROOT}/.. 12 | PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates 13 | PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} 14 | SKIP_INSTALL = YES 15 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES 16 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-EasyListViewExample/Pods-EasyListViewExample-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | ${PRODUCT_BUNDLE_IDENTIFIER} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | ${PRODUCT_NAME} 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | ${CURRENT_PROJECT_VERSION} 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-EasyListViewExample/Pods-EasyListViewExample-acknowledgements.markdown: -------------------------------------------------------------------------------- 1 | # Acknowledgements 2 | This application makes use of the following third party libraries: 3 | 4 | ## EasyCompatible 5 | 6 | MIT License 7 | 8 | Copyright (c) 2022 moliya 9 | 10 | Permission is hereby granted, free of charge, to any person obtaining a copy 11 | of this software and associated documentation files (the "Software"), to deal 12 | in the Software without restriction, including without limitation the rights 13 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | copies of the Software, and to permit persons to whom the Software is 15 | furnished to do so, subject to the following conditions: 16 | 17 | The above copyright notice and this permission notice shall be included in all 18 | copies or substantial portions of the Software. 19 | 20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | SOFTWARE. 27 | 28 | 29 | ## EasyListView 30 | 31 | MIT License 32 | 33 | Copyright (c) 2020 moliya 34 | 35 | Permission is hereby granted, free of charge, to any person obtaining a copy 36 | of this software and associated documentation files (the "Software"), to deal 37 | in the Software without restriction, including without limitation the rights 38 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 39 | copies of the Software, and to permit persons to whom the Software is 40 | furnished to do so, subject to the following conditions: 41 | 42 | The above copyright notice and this permission notice shall be included in all 43 | copies or substantial portions of the Software. 44 | 45 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 46 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 47 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 48 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 49 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 50 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 51 | SOFTWARE. 52 | 53 | Generated by CocoaPods - https://cocoapods.org 54 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-EasyListViewExample/Pods-EasyListViewExample-acknowledgements.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreferenceSpecifiers 6 | 7 | 8 | FooterText 9 | This application makes use of the following third party libraries: 10 | Title 11 | Acknowledgements 12 | Type 13 | PSGroupSpecifier 14 | 15 | 16 | FooterText 17 | MIT License 18 | 19 | Copyright (c) 2022 moliya 20 | 21 | Permission is hereby granted, free of charge, to any person obtaining a copy 22 | of this software and associated documentation files (the "Software"), to deal 23 | in the Software without restriction, including without limitation the rights 24 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 25 | copies of the Software, and to permit persons to whom the Software is 26 | furnished to do so, subject to the following conditions: 27 | 28 | The above copyright notice and this permission notice shall be included in all 29 | copies or substantial portions of the Software. 30 | 31 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 32 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 33 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 34 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 35 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 36 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 37 | SOFTWARE. 38 | 39 | License 40 | MIT 41 | Title 42 | EasyCompatible 43 | Type 44 | PSGroupSpecifier 45 | 46 | 47 | FooterText 48 | MIT License 49 | 50 | Copyright (c) 2020 moliya 51 | 52 | Permission is hereby granted, free of charge, to any person obtaining a copy 53 | of this software and associated documentation files (the "Software"), to deal 54 | in the Software without restriction, including without limitation the rights 55 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 56 | copies of the Software, and to permit persons to whom the Software is 57 | furnished to do so, subject to the following conditions: 58 | 59 | The above copyright notice and this permission notice shall be included in all 60 | copies or substantial portions of the Software. 61 | 62 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 63 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 64 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 65 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 66 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 67 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 68 | SOFTWARE. 69 | 70 | License 71 | MIT 72 | Title 73 | EasyListView 74 | Type 75 | PSGroupSpecifier 76 | 77 | 78 | FooterText 79 | Generated by CocoaPods - https://cocoapods.org 80 | Title 81 | 82 | Type 83 | PSGroupSpecifier 84 | 85 | 86 | StringsTable 87 | Acknowledgements 88 | Title 89 | Acknowledgements 90 | 91 | 92 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-EasyListViewExample/Pods-EasyListViewExample-dummy.m: -------------------------------------------------------------------------------- 1 | #import 2 | @interface PodsDummy_Pods_EasyListViewExample : NSObject 3 | @end 4 | @implementation PodsDummy_Pods_EasyListViewExample 5 | @end 6 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-EasyListViewExample/Pods-EasyListViewExample-frameworks-Debug-input-files.xcfilelist: -------------------------------------------------------------------------------- 1 | ${PODS_ROOT}/Target Support Files/Pods-EasyListViewExample/Pods-EasyListViewExample-frameworks.sh 2 | ${BUILT_PRODUCTS_DIR}/EasyCompatible/EasyCompatible.framework 3 | ${BUILT_PRODUCTS_DIR}/EasyListView/EasyListView.framework -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-EasyListViewExample/Pods-EasyListViewExample-frameworks-Debug-output-files.xcfilelist: -------------------------------------------------------------------------------- 1 | ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/EasyCompatible.framework 2 | ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/EasyListView.framework -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-EasyListViewExample/Pods-EasyListViewExample-frameworks-Release-input-files.xcfilelist: -------------------------------------------------------------------------------- 1 | ${PODS_ROOT}/Target Support Files/Pods-EasyListViewExample/Pods-EasyListViewExample-frameworks.sh 2 | ${BUILT_PRODUCTS_DIR}/EasyCompatible/EasyCompatible.framework 3 | ${BUILT_PRODUCTS_DIR}/EasyListView/EasyListView.framework -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-EasyListViewExample/Pods-EasyListViewExample-frameworks-Release-output-files.xcfilelist: -------------------------------------------------------------------------------- 1 | ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/EasyCompatible.framework 2 | ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/EasyListView.framework -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-EasyListViewExample/Pods-EasyListViewExample-umbrella.h: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | 14 | FOUNDATION_EXPORT double Pods_EasyListViewExampleVersionNumber; 15 | FOUNDATION_EXPORT const unsigned char Pods_EasyListViewExampleVersionString[]; 16 | 17 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-EasyListViewExample/Pods-EasyListViewExample.debug.xcconfig: -------------------------------------------------------------------------------- 1 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES 2 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO 3 | FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/EasyCompatible" "${PODS_CONFIGURATION_BUILD_DIR}/EasyListView" 4 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 5 | HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/EasyCompatible/EasyCompatible.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/EasyListView/EasyListView.framework/Headers" 6 | LD_RUNPATH_SEARCH_PATHS = $(inherited) /usr/lib/swift '@executable_path/Frameworks' '@loader_path/Frameworks' 7 | LIBRARY_SEARCH_PATHS = $(inherited) "${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift 8 | OTHER_LDFLAGS = $(inherited) -framework "EasyCompatible" -framework "EasyListView" 9 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS 10 | PODS_BUILD_DIR = ${BUILD_DIR} 11 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 12 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/. 13 | PODS_ROOT = ${SRCROOT}/Pods 14 | PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates 15 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES 16 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-EasyListViewExample/Pods-EasyListViewExample.modulemap: -------------------------------------------------------------------------------- 1 | framework module Pods_EasyListViewExample { 2 | umbrella header "Pods-EasyListViewExample-umbrella.h" 3 | 4 | export * 5 | module * { export * } 6 | } 7 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-EasyListViewExample/Pods-EasyListViewExample.release.xcconfig: -------------------------------------------------------------------------------- 1 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES 2 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO 3 | FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/EasyCompatible" "${PODS_CONFIGURATION_BUILD_DIR}/EasyListView" 4 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 5 | HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/EasyCompatible/EasyCompatible.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/EasyListView/EasyListView.framework/Headers" 6 | LD_RUNPATH_SEARCH_PATHS = $(inherited) /usr/lib/swift '@executable_path/Frameworks' '@loader_path/Frameworks' 7 | LIBRARY_SEARCH_PATHS = $(inherited) "${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift 8 | OTHER_LDFLAGS = $(inherited) -framework "EasyCompatible" -framework "EasyListView" 9 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS 10 | PODS_BUILD_DIR = ${BUILD_DIR} 11 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 12 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/. 13 | PODS_ROOT = ${SRCROOT}/Pods 14 | PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates 15 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES 16 | -------------------------------------------------------------------------------- /Sources/ObjC/EasyListObjC.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EasyListObjC.swift 3 | // EasyListViewExample 4 | // 5 | // Created by carefree on 2020/8/18. 6 | // Copyright © 2020 carefree. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @objc open class EasyListObjCAttributes: NSObject { 12 | fileprivate var attributes: EasyListAttributes 13 | 14 | init(_ attributes: EasyListAttributes) { 15 | self.attributes = attributes 16 | super.init() 17 | } 18 | } 19 | 20 | @objc public extension EasyListObjCAttributes { 21 | /** 22 | 设置唯一标识 23 | 24 | * parameter identifier: 标识字符串 25 | 26 | * returns: 自定义配置项 27 | */ 28 | var identifier: (String) -> EasyListObjCAttributes { 29 | return { identifier in 30 | self.attributes.identifier(identifier) 31 | return self 32 | } 33 | } 34 | 35 | /** 36 | 设置内间距 37 | 38 | * parameter insets: 内间距 39 | 40 | * returns: 自定义配置项 41 | */ 42 | var insets: (UIEdgeInsets) -> EasyListObjCAttributes { 43 | return { insets in 44 | self.attributes.insets(insets) 45 | return self 46 | } 47 | } 48 | 49 | /** 50 | 设置与上一元素的间距 51 | 52 | * parameter spacing: 间距 53 | 54 | * returns: 自定义配置项 55 | */ 56 | var spacing: (CGFloat) -> EasyListObjCAttributes { 57 | return { spacing in 58 | self.attributes.spacing(spacing) 59 | return self 60 | } 61 | } 62 | 63 | /** 64 | 设置超出部分是否裁剪 65 | 66 | * parameter clipsToBounds: 是否裁剪 67 | 68 | * returns: 自定义配置项 69 | */ 70 | var clipsToBounds: (Bool) -> EasyListObjCAttributes { 71 | return { clipsToBounds in 72 | self.attributes.clipsToBounds(clipsToBounds) 73 | return self 74 | } 75 | } 76 | } 77 | 78 | @available(*, unavailable) 79 | @objc public extension UIScrollView { 80 | // MARK: - Coordinator 81 | var easy_coordinator: EasyListCoordinator { 82 | get { 83 | return easy.coordinator 84 | } 85 | set { 86 | easy.coordinator = newValue 87 | } 88 | } 89 | 90 | // MARK: - Append 91 | /** 92 | 添加一个视图元素 93 | 94 | * parameter view: 视图 95 | 96 | * returns: 自定义配置项 97 | */ 98 | @discardableResult 99 | func easy_appendView(_ view: UIView) -> EasyListObjCAttributes { 100 | let attributes = easy.appendView(view) 101 | return EasyListObjCAttributes(attributes) 102 | } 103 | 104 | /** 105 | 添加一个视图元素 106 | 107 | * parameter block: 视图block 108 | 109 | * returns: 自定义配置项 110 | */ 111 | @discardableResult 112 | func easy_appendViewBy(_ block: () -> UIView) -> EasyListObjCAttributes { 113 | let attributes = easy.appendView(block) 114 | return EasyListObjCAttributes(attributes) 115 | } 116 | 117 | // MARK: - Insert 118 | /** 119 | 在目标之后插入一个视图元素 120 | 121 | * parameter view: 视图 122 | * parameter element: 前一个视图元素,可以是UIView,也可以是视图唯一标识 123 | 124 | * returns: 自定义配置项 125 | */ 126 | @discardableResult 127 | func easy_insertView(_ view: UIView, after element: Any) -> EasyListObjCAttributes { 128 | let attributes = easy.insertView(view, after: element) 129 | return EasyListObjCAttributes(attributes) 130 | } 131 | 132 | /** 133 | 在目标之后插入一个视图元素 134 | 135 | * parameter block: 视图block 136 | * parameter element: 前一个视图元素,可以是UIView,也可以是视图唯一标识 137 | 138 | * returns: 自定义配置项 139 | */ 140 | @discardableResult 141 | func easy_insertViewBy(_ block: () -> UIView, after element: Any) -> EasyListObjCAttributes { 142 | let attributes = easy.insertView(block, after: element) 143 | return EasyListObjCAttributes(attributes) 144 | } 145 | 146 | /** 147 | 在目标之前插入一个视图元素 148 | 149 | * parameter view: 视图 150 | * parameter element: 后一个视图元素,可以是UIView,也可以是视图唯一标识 151 | 152 | * returns: 自定义配置项 153 | */ 154 | @discardableResult 155 | func easy_insertView(_ view: UIView, before element: Any) -> EasyListObjCAttributes { 156 | let attributes = easy.insertView(view, before: element) 157 | return EasyListObjCAttributes(attributes) 158 | } 159 | 160 | /** 161 | 在目标之前插入一个视图元素 162 | 163 | * parameter block: 视图block 164 | * parameter element: 后一个视图元素,可以是UIView,也可以是视图唯一标识 165 | 166 | * returns: 自定义配置项 167 | */ 168 | @discardableResult 169 | func easy_insertViewBy(_ block: () -> UIView, before element: Any) -> EasyListObjCAttributes { 170 | let attributes = easy.insertView(block, before: element) 171 | return EasyListObjCAttributes(attributes) 172 | } 173 | 174 | // MARK: - Delete 175 | /** 176 | 删除一个视图元素 177 | 178 | * parameter view: 要删除的视图,可以是UIView,也可以是视图的唯一标识 179 | */ 180 | func easy_deleteView(_ view: Any) { 181 | easy.deleteView(view) 182 | } 183 | 184 | /** 185 | 删除一个视图元素 186 | 187 | * parameter view: 要删除的视图,可以是UIView,也可以是视图的唯一标识 188 | * parameter completion: 删除完成后的回调 189 | */ 190 | func easy_deleteView(_ view: Any, completion: (() -> Void)?) { 191 | easy.deleteView(view, completion: completion) 192 | } 193 | 194 | /** 195 | 删除所有视图元素 196 | 197 | */ 198 | func easy_deleteAll() { 199 | easy.deleteAll() 200 | } 201 | 202 | // MARK: - BatchUpdate 203 | /** 204 | 开始批量更新操作 205 | 206 | * Note: 需和endUpdates成对使用。 207 | */ 208 | func easy_beginUpdates() { 209 | easy.beginUpdates() 210 | } 211 | 212 | /** 213 | 开始批量更新操作 214 | 215 | * parameter option: 更新方式 216 | * Note: 需和endUpdates成对使用。 217 | */ 218 | func easy_beginUpdates(option: EasyListUpdateOption) { 219 | easy.beginUpdates(option: option) 220 | } 221 | 222 | /** 223 | 完成批量更新操作 224 | 225 | */ 226 | func easy_endUpdates() { 227 | easy.endUpdates() 228 | } 229 | 230 | /** 231 | 完成批量更新操作 232 | 233 | * parameter completion: 更新完成后的回调 234 | */ 235 | func easy_endUpdates(completion: (() -> Void)?) { 236 | easy.endUpdates(completion) 237 | } 238 | 239 | // MARK: - Disposable 240 | /** 241 | 生成自释放元素 242 | 243 | * parameter maker: 闭包 244 | 245 | * returns: 生成的视图 246 | */ 247 | func easy_disposableView(maker: @escaping () -> UIView) -> UIView { 248 | return easy.disposableView(with: maker) 249 | } 250 | 251 | /** 252 | 刷新数据 253 | 254 | */ 255 | func easy_reloadDisposableData() { 256 | easy.reloadDisposableData() 257 | } 258 | 259 | /** 260 | 触发自释放机制 261 | 262 | */ 263 | func easy_triggerDisposable() { 264 | easy.triggerDisposable() 265 | } 266 | 267 | // MARK: - Getter 268 | /** 269 | 获取视图元素 270 | 271 | * parameter identifier: 视图唯一标识 272 | 273 | * returns: 找到的视图 274 | */ 275 | func easy_getElement(identifier: String) -> AnyObject? { 276 | return easy.getElement(identifier: identifier) 277 | } 278 | 279 | /** 280 | 获取指定下标的自释放元素 281 | 282 | * parameter index: 下标 283 | 284 | * returns: 找到的视图 285 | */ 286 | func easy_getDisposableElementAtIndex(_ index: Int) -> AnyObject? { 287 | return easy.getDisposableElement(at: index) 288 | } 289 | 290 | /** 291 | 获取所有可视的自释放元素 292 | 293 | * returns: 找到的视图集合 294 | */ 295 | var easy_visibleDisposableElements: [AnyObject] { 296 | return easy.visibleDisposableElements 297 | } 298 | } 299 | -------------------------------------------------------------------------------- /Sources/ObjC/EasyListObjCDeprecated.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EasyListObjCDeprecated.swift 3 | // EasyListViewExample 4 | // 5 | // Created by carefree on 2022/10/8. 6 | // Copyright © 2022 carefree. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @available(*, unavailable) 12 | @objc public extension UIScrollView { 13 | // MARK: - Append 14 | @available(*, deprecated, message: "Please use easy_appendView: instead.") 15 | func easy_appendView(_ view: UIView, spacing: CGFloat) { 16 | easy.append(view, spacing: spacing) 17 | } 18 | 19 | @available(*, deprecated, message: "Please use easy_appendViewBy: instead.") 20 | func easy_appendViewBy(_ block: () -> UIView, spacing: CGFloat) { 21 | easy.append(block(), spacing: spacing) 22 | } 23 | 24 | @available(*, deprecated, message: "Please use easy_appendView: instead.") 25 | func easy_appendView(_ view: UIView, forIdentifier identifier: String, spacing: CGFloat) { 26 | easy.append(view, for: identifier, spacing: spacing) 27 | } 28 | 29 | @available(*, deprecated, message: "Please use easy_appendViewBy: instead.") 30 | func easy_appendViewBy(_ block: () -> UIView, forIdentifier identifier: String, spacing: CGFloat) { 31 | easy.append(block(), for: identifier, spacing: spacing) 32 | } 33 | 34 | @available(*, deprecated, message: "Please use easy_appendView: instead.") 35 | func easy_appendView(_ view: UIView, forIdentifier identifier: String, withInsets insets: UIEdgeInsets) { 36 | easy.append(view, with: insets, for: identifier) 37 | } 38 | 39 | @available(*, deprecated, message: "Please use easy_appendViewBy: instead.") 40 | func easy_appendViewBy(_ block: () -> UIView, forIdentifier identifier: String, withInsets insets: UIEdgeInsets) { 41 | easy.append(block(), with: insets, for: identifier) 42 | } 43 | 44 | // MARK: - Insert 45 | @available(*, deprecated, message: "Please use easy_insertView:after: instead.") 46 | func easy_insertView(_ view: UIView, after element: Any, withInsets insets: UIEdgeInsets) { 47 | easy.insert(view, after: element, with: insets) 48 | } 49 | 50 | @available(*, deprecated, message: "Please use easy_insertView:after: instead.") 51 | func easy_insertViewBy(_ block: () -> UIView, after element: Any, withInsets insets: UIEdgeInsets) { 52 | easy.insert(block(), after: element, with: insets) 53 | } 54 | 55 | @available(*, deprecated, message: "Please use easy_insertView:after: instead.") 56 | func easy_insertView(_ view: UIView, after element: Any, withInsets insets: UIEdgeInsets, forIdentifier identifier: String) { 57 | easy.insert(view, after: element, with: insets, for: identifier) 58 | } 59 | 60 | @available(*, deprecated, message: "Please use easy_insertView:after: instead.") 61 | func easy_insertViewBy(_ block: () -> UIView, after element: Any, withInsets insets: UIEdgeInsets, forIdentifier identifier: String) { 62 | easy.insert(block(), after: element, with: insets, for: identifier) 63 | } 64 | 65 | @available(*, deprecated, message: "Please use easy_insertView:after: instead.") 66 | func easy_insertView(_ view: UIView, after element: Any, withInsets insets: UIEdgeInsets, forIdentifier identifier: String, completion: (() -> Void)?) { 67 | easy.insert(view, after: element, with: insets, for: identifier, completion: completion) 68 | } 69 | 70 | @available(*, deprecated, message: "Please use easy_insertView:after: instead.") 71 | func easy_insertViewBy(_ block: () -> UIView, after element: Any, withInsets insets: UIEdgeInsets, forIdentifier identifier: String, completion: (() -> Void)?) { 72 | easy.insert(block(), after: element, with: insets, for: identifier, completion: completion) 73 | } 74 | 75 | @available(*, deprecated, message: "Please use easy_insertView:before: instead.") 76 | func easy_insertView(_ view: UIView, before element: Any, withInsets insets: UIEdgeInsets) { 77 | easy.insert(view, before: element, with: insets) 78 | } 79 | 80 | @available(*, deprecated, message: "Please use easy_insertView:before: instead.") 81 | func easy_insertViewBy(_ block: () -> UIView, before element: Any, withInsets insets: UIEdgeInsets) { 82 | easy.insert(block(), before: element, with: insets) 83 | } 84 | 85 | @available(*, deprecated, message: "Please use easy_insertView:before: instead.") 86 | func easy_insertView(_ view: UIView, before element: Any, withInsets insets: UIEdgeInsets, forIdentifier identifier: String) { 87 | easy.insert(view, before: element, with: insets, for: identifier) 88 | } 89 | 90 | @available(*, deprecated, message: "Please use easy_insertView:before: instead.") 91 | func easy_insertViewBy(_ block: () -> UIView, before element: Any, withInsets insets: UIEdgeInsets, forIdentifier identifier: String) { 92 | easy.insert(block(), before: element, with: insets, for: identifier) 93 | } 94 | 95 | @available(*, deprecated, message: "Please use easy_insertView:before: instead.") 96 | func easy_insertView(_ view: UIView, before element: Any, withInsets insets: UIEdgeInsets, forIdentifier identifier: String, completion: (() -> Void)?) { 97 | easy.insert(view, before: element, with: insets, for: identifier, completion: completion) 98 | } 99 | 100 | @available(*, deprecated, message: "Please use easy_insertView:before: instead.") 101 | func easy_insertViewBy(_ block: () -> UIView, before element: Any, withInsets insets: UIEdgeInsets, forIdentifier identifier: String, completion: (() -> Void)?) { 102 | easy.insert(block(), before: element, with: insets, for: identifier, completion: completion) 103 | } 104 | 105 | // MARK: - Delete 106 | @available(*, deprecated, message: "Please use easy_deleteView: instead.") 107 | func easy_deleteElement(_ element: Any) { 108 | easy.delete(element) 109 | } 110 | 111 | @available(*, deprecated, message: "Please use easy_deleteView:completion: instead.") 112 | func easy_deleteElement(_ element: Any, completion: (() -> Void)?) { 113 | easy.delete(element, completion: completion) 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /Sources/Swift/EasyListAppend.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EasyListAppend.swift 3 | // EasyListViewExample 4 | // 5 | // Created by carefree on 2022/10/7. 6 | // Copyright © 2022 carefree. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import EasyCompatible 11 | 12 | public extension EasyExtension where Base: UIScrollView { 13 | /** 14 | 添加一个视图元素 15 | 16 | * parameter viewClosure: 视图闭包 17 | 18 | * returns: 自定义配置项 19 | */ 20 | @discardableResult 21 | func appendView(_ viewClosure: () -> UIView) -> EasyListAttributes { 22 | return appendView(viewClosure()) 23 | } 24 | 25 | /** 26 | 添加一个视图元素 27 | 28 | * parameter view: 视图 29 | 30 | * returns: 自定义配置项 31 | */ 32 | @discardableResult 33 | func appendView(_ view: UIView) -> EasyListAttributes { 34 | let scrollView = base 35 | 36 | //移除旧的约束 37 | searchConstraintsIn(scrollView, with: [ 38 | .first(scrollView, .bottom), 39 | .second(scrollView, .bottom) 40 | ]).forEach { $0.isActive = false } 41 | //添加子视图 42 | var contentView = EasyListContentView() 43 | var isDisposable = false 44 | if let disposableView = view as? EasyListContentView { 45 | //动态元素 46 | contentView = disposableView 47 | isDisposable = true 48 | } else { 49 | //静态元素 50 | var staticView = view 51 | if let cell = staticView as? UITableViewCell { 52 | staticView = cell.contentView 53 | coordinator.cells[contentView] = cell 54 | } 55 | staticView.translatesAutoresizingMaskIntoConstraints = false 56 | contentView.addSubview(staticView) 57 | } 58 | 59 | guard let view = contentView.subviews.first else { 60 | return EasyListAttributes() 61 | } 62 | 63 | var insets = coordinator.globalEdgeInsets 64 | insets.top = insets.top + coordinator.globalSpacing 65 | 66 | let leadingConstraint = addConstraint(for: contentView, item1: view, attr1: .leading, item2: contentView, attr2: .leading, constant: insets.left) 67 | let trailingConstraint = addConstraint(for: contentView, item1: view, attr1: .trailing, item2: contentView, attr2: .trailing, constant: -insets.right) 68 | let topConstraint = addConstraint(for: contentView, item1: view, attr1: .top, item2: contentView, attr2: .top, constant: insets.top) 69 | let bottomConstraint = addConstraint(for: contentView, item1: view, attr1: .bottom, item2: contentView, attr2: .bottom, constant: -insets.bottom) 70 | 71 | contentView.clipsToBounds = coordinator.globalClipsToBounds 72 | contentView.translatesAutoresizingMaskIntoConstraints = false 73 | scrollView.addSubview(contentView) 74 | 75 | addConstraint(for: scrollView, item1: scrollView, attr1: .width, item2: contentView, attr2: .width) 76 | addConstraint(for: scrollView, item1: contentView, attr1: .leading, item2: scrollView, attr2: .leading) 77 | addConstraint(for: scrollView, item1: contentView, attr1: .trailing, item2: scrollView, attr2: .trailing) 78 | addConstraint(for: scrollView, item1: contentView, attr1: .bottom, item2: scrollView, attr2: .bottom) 79 | if let lastView = coordinator.elements.last?.view { 80 | addConstraint(for: scrollView, item1: contentView, attr1: .top, item2: lastView, attr2: .bottom) 81 | } else { 82 | addConstraint(for: scrollView, item1: contentView, attr1: .top, item2: scrollView, attr2: .top) 83 | } 84 | 85 | var element = Element(view: contentView, insets: insets) 86 | coordinator.elements.append(element) 87 | if isDisposable { 88 | coordinator.disposableElements.append(Element(view: contentView, insets: insets)) 89 | } 90 | 91 | if coordinator.onBatchUpdate { 92 | element.inserting = true 93 | } 94 | 95 | var attributes = EasyListAttributes() 96 | attributes.coordinator = coordinator 97 | attributes.element = element 98 | attributes.leadingConstraint = leadingConstraint 99 | attributes.trailingConstraint = trailingConstraint 100 | attributes.topConstraint = topConstraint 101 | attributes.bottomConstraint = bottomConstraint 102 | 103 | return attributes 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /Sources/Swift/EasyListAppendDeprecated.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EasyListAppendDeprecated.swift 3 | // EasyListViewExample 4 | // 5 | // Created by carefree on 2022/10/7. 6 | // Copyright © 2022 carefree. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import EasyCompatible 11 | 12 | public extension EasyExtension where Base: UIScrollView { 13 | /** 14 | 添加一个视图元素 15 | 16 | * parameter viewOrClosure: 视图或闭包 17 | * parameter spacing: 与上一个视图的间距 18 | */ 19 | @available(*, deprecated, renamed: "appendView(_:)", message: "Please use appendView(_:) instead.") 20 | func append(_ viewOrClosure: Any, spacing: CGFloat = 0) { 21 | var inset = coordinator.globalEdgeInsets 22 | inset.top = spacing 23 | append(viewOrClosure, with: inset) 24 | } 25 | 26 | /** 27 | 添加一个视图元素 28 | 29 | * parameter viewOrClosure: 视图或闭包 30 | * parameter identifier: 视图唯一标识 31 | * parameter spacing: 与上一个视图的间距 32 | */ 33 | @available(*, deprecated, renamed: "appendView(_:)", message: "Please use appendView(_:) instead.") 34 | func append(_ viewOrClosure: Any, for identifier: String = "", spacing: CGFloat = 0) { 35 | var inset = coordinator.globalEdgeInsets 36 | inset.top = spacing 37 | append(viewOrClosure, with: inset, for: identifier) 38 | } 39 | 40 | /** 41 | 添加一个视图元素 42 | 43 | * parameter viewOrClosure: 视图或闭包 44 | * parameter insets: 视图自定义的间距 45 | * parameter identifier: 视图唯一标识 46 | */ 47 | @available(*, deprecated, renamed: "appendView(_:)", message: "Please use appendView(_:) instead.") 48 | func append(_ viewOrClosure: Any, with insets: UIEdgeInsets, for identifier: String = "") { 49 | let scrollView = base 50 | 51 | //移除旧的约束 52 | searchConstraintsIn(scrollView, with: [ 53 | .first(scrollView, .bottom), 54 | .second(scrollView, .bottom) 55 | ]).forEach { $0.isActive = false } 56 | //添加子视图 57 | var contentView = EasyListContentView() 58 | var isDisposable = false 59 | if let disposableView = viewOrClosure as? EasyListContentView { 60 | //动态元素 61 | contentView = disposableView 62 | isDisposable = true 63 | } else if let staticView = viewOrClosure as? UIView { 64 | //静态元素 65 | var view = staticView 66 | if let cell = view as? UITableViewCell { 67 | view = cell.contentView 68 | coordinator.cells[contentView] = cell 69 | } 70 | view.translatesAutoresizingMaskIntoConstraints = false 71 | contentView.addSubview(view) 72 | } else if let closure = viewOrClosure as? () -> UIView { 73 | //闭包 74 | var view = closure() 75 | if let cell = view as? UITableViewCell { 76 | view = cell.contentView 77 | coordinator.cells[contentView] = cell 78 | } 79 | view.translatesAutoresizingMaskIntoConstraints = false 80 | contentView.addSubview(view) 81 | } 82 | 83 | guard let view = contentView.subviews.first else { return } 84 | 85 | addConstraint(for: contentView, item1: view, attr1: .leading, item2: contentView, attr2: .leading, constant: insets.left) 86 | addConstraint(for: contentView, item1: view, attr1: .trailing, item2: contentView, attr2: .trailing, constant: -insets.right) 87 | addConstraint(for: contentView, item1: view, attr1: .top, item2: contentView, attr2: .top, constant: insets.top) 88 | addConstraint(for: contentView, item1: view, attr1: .bottom, item2: contentView, attr2: .bottom, constant: -insets.bottom) 89 | 90 | contentView.clipsToBounds = true 91 | contentView.translatesAutoresizingMaskIntoConstraints = false 92 | scrollView.addSubview(contentView) 93 | 94 | addConstraint(for: scrollView, item1: scrollView, attr1: .width, item2: contentView, attr2: .width) 95 | addConstraint(for: scrollView, item1: contentView, attr1: .leading, item2: scrollView, attr2: .leading) 96 | addConstraint(for: scrollView, item1: contentView, attr1: .trailing, item2: scrollView, attr2: .trailing) 97 | addConstraint(for: scrollView, item1: contentView, attr1: .bottom, item2: scrollView, attr2: .bottom) 98 | if let lastView = coordinator.elements.last?.view { 99 | addConstraint(for: scrollView, item1: contentView, attr1: .top, item2: lastView, attr2: .bottom) 100 | } else { 101 | addConstraint(for: scrollView, item1: contentView, attr1: .top, item2: scrollView, attr2: .top) 102 | } 103 | 104 | var element = Element(view: contentView, insets: insets, identifier: identifier) 105 | coordinator.elements.append(element) 106 | if isDisposable { 107 | coordinator.disposableElements.append(Element(view: contentView, insets: insets, identifier: identifier)) 108 | } 109 | 110 | if coordinator.onBatchUpdate { 111 | element.inserting = true 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /Sources/Swift/EasyListAttributes.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EasyListAttributes.swift 3 | // EasyListView 4 | // 5 | // Created by carefree on 2022/10/9. 6 | // 7 | 8 | import UIKit 9 | 10 | public struct EasyListAttributes { 11 | internal var coordinator: EasyListCoordinator? 12 | internal var element: Element? 13 | internal var leadingConstraint: NSLayoutConstraint? 14 | internal var trailingConstraint: NSLayoutConstraint? 15 | internal var topConstraint: NSLayoutConstraint? 16 | internal var bottomConstraint: NSLayoutConstraint? 17 | } 18 | 19 | public extension EasyListAttributes { 20 | /** 21 | 设置唯一标识 22 | 23 | * parameter identifier: 标识字符串 24 | 25 | * returns: 自定义配置项 26 | */ 27 | @discardableResult 28 | func identifier(_ identifier: String) -> Self { 29 | if let coordinator = coordinator, let element = element { 30 | if let index = coordinator.elements.firstIndex(of: element) { 31 | var newElement = coordinator.elements[index] 32 | newElement.identifier = identifier 33 | coordinator.elements[index] = newElement 34 | } 35 | if let index = coordinator.disposableElements.firstIndex(of: element) { 36 | var newElement = coordinator.elements[index] 37 | newElement.identifier = identifier 38 | coordinator.elements[index] = newElement 39 | } 40 | } 41 | 42 | return self 43 | } 44 | 45 | /** 46 | 设置内间距 47 | 48 | * parameter insets: 内间距 49 | 50 | * returns: 自定义配置项 51 | */ 52 | @discardableResult 53 | func insets(_ insets: UIEdgeInsets) -> Self { 54 | leadingConstraint?.constant = insets.left 55 | trailingConstraint?.constant = -insets.right 56 | topConstraint?.constant = insets.top 57 | bottomConstraint?.constant = -insets.bottom 58 | return self 59 | } 60 | 61 | /** 62 | 设置与上一元素的间距 63 | 64 | * parameter spacing: 间距 65 | 66 | * returns: 自定义配置项 67 | */ 68 | @discardableResult 69 | func spacing(_ spacing: CGFloat) -> Self { 70 | topConstraint?.constant = spacing 71 | return self 72 | } 73 | 74 | /** 75 | 设置超出部分是否裁剪 76 | 77 | * parameter clipsToBounds: 是否裁剪 78 | 79 | * returns: 自定义配置项 80 | */ 81 | @discardableResult 82 | func clipsToBounds(_ clipsToBounds: Bool) -> Self { 83 | element?.view.clipsToBounds = clipsToBounds 84 | return self 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /Sources/Swift/EasyListConstraint.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EasyListConstraint.swift 3 | // EasyListViewExample 4 | // 5 | // Created by carefree on 2020/8/6. 6 | // Copyright © 2020 carefree. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | // 添加约束 12 | @discardableResult 13 | internal func addConstraint(for view: UIView, 14 | item1: AnyObject, 15 | attr1: NSLayoutConstraint.Attribute, 16 | item2: AnyObject? = nil, 17 | attr2: NSLayoutConstraint.Attribute? = nil, 18 | constant: CGFloat = 0) -> NSLayoutConstraint { 19 | let c = NSLayoutConstraint( 20 | item: item1, 21 | attribute: attr1, 22 | relatedBy: .equal, 23 | toItem: item2, 24 | attribute: ((attr2 == nil) ? attr1 : attr2! ), 25 | multiplier: 1, 26 | constant: constant 27 | ) 28 | c.priority = UILayoutPriority(rawValue: UILayoutPriority.defaultHigh.rawValue + 1) 29 | view.addConstraint(c) 30 | return c 31 | } 32 | -------------------------------------------------------------------------------- /Sources/Swift/EasyListCoordinator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EasyListCoordinator.swift 3 | // EasyListViewExample 4 | // 5 | // Created by carefree on 2020/8/8. 6 | // Copyright © 2020 carefree. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @objc(EasyListUpdateOption) 12 | public enum EasyListUpdateOption: Int { 13 | case animatedLayout = 0 //带动画的布局更新 14 | case onlyLayout //无动画的布局更新 15 | case noLayout //不进行布局 16 | } 17 | 18 | internal struct Element: Equatable { 19 | var view: EasyListContentView 20 | var insets: UIEdgeInsets = .zero 21 | var identifier: String? 22 | var deleting: Bool = false 23 | var inserting: Bool = false 24 | 25 | static func == (lhs: Self, rhs: Self) -> Bool { 26 | return lhs.view == rhs.view 27 | } 28 | } 29 | 30 | @objcMembers 31 | open class EasyListCoordinator: NSObject { 32 | 33 | public weak private(set) var scrollView: UIScrollView? 34 | 35 | //全局内边距 36 | public var globalEdgeInsets: UIEdgeInsets = .zero 37 | //全局元素间距 38 | public var globalSpacing: CGFloat = 0 39 | //超出部分裁剪,默认为true 40 | public var globalClipsToBounds: Bool = true 41 | //动画持续时间(包括插入、删除),为0则无动画 42 | public var animationDuration: TimeInterval = 0.3 43 | 44 | internal var elements = [Element]() 45 | internal var disposableElements = [Element]() 46 | internal var cells = [EasyListContentView: UITableViewCell]() 47 | internal var onBatchUpdate = false 48 | internal var batchUpdateOption: EasyListUpdateOption = .animatedLayout 49 | 50 | @objc(initWithScrollView:) 51 | public init(with scrollView: UIScrollView) { 52 | self.scrollView = scrollView 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Sources/Swift/EasyListDelete.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EasyListDelete.swift 3 | // EasyListViewExample 4 | // 5 | // Created by carefree on 2022/10/7. 6 | // Copyright © 2022 carefree. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import EasyCompatible 11 | 12 | public extension EasyExtension where Base: UIScrollView { 13 | /** 14 | 删除一个视图元素 15 | 16 | * parameter view: 要删除的视图,可以是UIView,也可以是视图的唯一标识 17 | * parameter completion: 删除完成后的回调 18 | */ 19 | func deleteView(_ view: Any, completion: (() -> Void)? = nil) { 20 | let elements = coordinator.elements 21 | 22 | var targetView: UIView? 23 | if let string = view as? String { 24 | targetView = elements.first { $0.identifier == string }?.view 25 | } 26 | if let cell = view as? UITableViewCell { 27 | targetView = coordinator.cells.first { $0.value == cell }?.key 28 | } else if let view = view as? UIView { 29 | targetView = elements.first { $0.view == view.superview }?.view 30 | } 31 | assert(targetView != nil, "invalid element") 32 | 33 | coordinator.elements = coordinator.elements.map { 34 | var tmp = $0 35 | if targetView == tmp.view && !tmp.deleting { 36 | tmp.deleting = true 37 | } 38 | return tmp 39 | } 40 | 41 | if coordinator.onBatchUpdate { 42 | completion?() 43 | return 44 | } 45 | 46 | animateDeletion(completion: { 47 | self.reloadDisposableIfNeed() 48 | completion?() 49 | }, duration: coordinator.animationDuration) 50 | } 51 | 52 | /** 53 | 删除所有视图元素 54 | 55 | */ 56 | func deleteAll() { 57 | coordinator.elements.forEach { 58 | $0.view.removeFromSuperview() 59 | } 60 | coordinator.elements.removeAll() 61 | coordinator.disposableElements.removeAll() 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /Sources/Swift/EasyListDeleteDeprecated.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EasyListDeleteDeprecated.swift 3 | // EasyListView 4 | // 5 | // Created by carefree on 2022/10/10. 6 | // 7 | 8 | import UIKit 9 | import EasyCompatible 10 | 11 | public extension EasyExtension where Base: UIScrollView { 12 | /** 13 | 删除一个视图元素 14 | 15 | * parameter element: 要删除的视图,可以是UIView,也可以是视图的唯一标识 16 | * parameter completion: 删除完成后的回调 17 | */ 18 | @available(*, deprecated, renamed: "deleteView(_:completion:)", message: "Please use deleteView(_:completion:) instead.") 19 | func delete(_ element: Any, completion: (() -> Void)? = nil) { 20 | let elements = coordinator.elements 21 | 22 | var targetView: UIView? 23 | if let string = element as? String { 24 | targetView = elements.first { $0.identifier == string }?.view 25 | } 26 | if let cell = element as? UITableViewCell { 27 | targetView = coordinator.cells.first { $0.value == cell }?.key 28 | } else if let view = element as? UIView { 29 | targetView = elements.first { $0.view == view.superview }?.view 30 | } 31 | assert(targetView != nil, "invalid element") 32 | 33 | coordinator.elements = coordinator.elements.map { 34 | var tmp = $0 35 | if targetView == tmp.view && !tmp.deleting { 36 | tmp.deleting = true 37 | } 38 | return tmp 39 | } 40 | 41 | if coordinator.onBatchUpdate { 42 | completion?() 43 | return 44 | } 45 | 46 | animateDeletion(completion: { 47 | self.reloadDisposableIfNeed() 48 | completion?() 49 | }, duration: coordinator.animationDuration) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Sources/Swift/EasyListDisposable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EasyListDisposable.swift 3 | // EasyListViewExample 4 | // 5 | // Created by carefree on 2022/10/8. 6 | // Copyright © 2022 carefree. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import EasyCompatible 11 | 12 | public extension EasyExtension where Base: UIScrollView { 13 | /** 14 | 生成自释放元素 15 | 16 | * parameter maker: 闭包 17 | 18 | * returns: 生成的视图 19 | */ 20 | func disposableView(with maker: @escaping () -> UIView) -> UIView { 21 | let contentView = EasyListContentView() 22 | var view = maker() 23 | if let cell = view as? UITableViewCell { 24 | view = cell.contentView 25 | coordinator.cells[contentView] = cell 26 | } 27 | view.translatesAutoresizingMaskIntoConstraints = false 28 | contentView.addSubview(view) 29 | contentView.disposableMaker = maker 30 | 31 | return contentView 32 | } 33 | 34 | /** 35 | 刷新数据 36 | 37 | */ 38 | func reloadDisposableData() { 39 | coordinator.disposableElements.forEach { 40 | $0.view.subviews.first?.removeFromSuperview() 41 | } 42 | reloadDisposableIfNeed() 43 | } 44 | 45 | /** 46 | 触发自释放机制 47 | 48 | */ 49 | func triggerDisposable() { 50 | let scrollView = base 51 | 52 | let offset = scrollView.contentOffset.y 53 | let range = scrollView.frame.height 54 | let minY = offset - range 55 | let maxY = offset + range + range 56 | 57 | coordinator.disposableElements.forEach { element in 58 | let contentView = element.view 59 | let frame = contentView.frame 60 | guard let maker = contentView.disposableMaker else { return } 61 | 62 | if frame.maxY >= minY && frame.minY <= maxY { 63 | //在可视范围内 64 | if contentView.subviews.count == 0 { 65 | //恢复子视图 66 | var view = maker() 67 | if let cell = view as? UITableViewCell { 68 | view = cell.contentView 69 | coordinator.cells[contentView] = cell 70 | } 71 | view.translatesAutoresizingMaskIntoConstraints = false 72 | contentView.addSubview(view) 73 | addConstraint(for: contentView, item1: view, attr1: .leading, item2: contentView, attr2: .leading, constant: element.insets.left) 74 | addConstraint(for: contentView, item1: view, attr1: .trailing, item2: contentView, attr2: .trailing, constant: -element.insets.right) 75 | addConstraint(for: contentView, item1: view, attr1: .top, item2: contentView, attr2: .top, constant: element.insets.top) 76 | addConstraint(for: contentView, item1: view, attr1: .bottom, item2: contentView, attr2: .bottom, constant: -element.insets.bottom) 77 | //删除height约束 78 | contentView.constraints.first { $0.firstAttribute == .height }?.isActive = false 79 | } 80 | } else { 81 | //不在可视范围内 82 | if contentView.subviews.count > 0 { 83 | //移除子视图,回收内存 84 | addConstraint(for: contentView, item1: contentView, attr1: .height, item2: nil, attr2: .notAnAttribute, constant: frame.height) 85 | coordinator.cells.removeValue(forKey: contentView) 86 | contentView.subviews.forEach { $0.removeFromSuperview() } 87 | } 88 | } 89 | } 90 | } 91 | 92 | /** 93 | 获取指定下标的自释放元素 94 | 95 | * parameter index: 下标 96 | 97 | * returns: 找到的视图 98 | */ 99 | func getDisposableElement(at index: Int) -> UIView? { 100 | if index >= coordinator.disposableElements.count { 101 | return nil 102 | } 103 | let contentView = coordinator.disposableElements[index].view 104 | if let cell = coordinator.cells[contentView] { 105 | return cell 106 | } 107 | return contentView.subviews.first 108 | } 109 | 110 | /** 111 | 获取所有可视的自释放元素 112 | 113 | * returns: 找到的视图集合 114 | */ 115 | var visibleDisposableElements: [UIView] { 116 | return coordinator.disposableElements.compactMap { 117 | let contentView = $0.view 118 | if let cell = coordinator.cells[contentView] { 119 | return cell 120 | } 121 | return contentView.subviews.first 122 | } 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /Sources/Swift/EasyListView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EasyListView.swift 3 | // EasyListViewExample 4 | // 5 | // Created by carefree on 2020/6/11. 6 | // Copyright © 2020 carefree. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @objcMembers 12 | open class EasyListView: UIScrollView { 13 | 14 | private var observation: NSKeyValueObservation? 15 | 16 | // MARK: - Lifecycle 17 | public required init?(coder: NSCoder) { 18 | super.init(coder: coder) 19 | commonInit() 20 | } 21 | 22 | public override init(frame: CGRect) { 23 | super.init(frame: frame) 24 | commonInit() 25 | } 26 | 27 | deinit { 28 | observation?.invalidate() 29 | observation = nil 30 | } 31 | 32 | // MARK: - Private 33 | private func commonInit() { 34 | observation = self.observe(\.contentOffset, options: [.initial, .new]) {[weak self] _, _ in 35 | self?.easy.triggerDisposable() 36 | } 37 | } 38 | } 39 | 40 | internal class EasyListContentView: UIView { 41 | 42 | var disposableMaker: (() -> UIView)? = nil 43 | 44 | } 45 | --------------------------------------------------------------------------------