├── .DS_Store ├── .gitignore ├── LICENSE ├── OverlayController.podspec ├── OverlayController.xcodeproj ├── project.pbxproj └── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── OverlayController ├── AppDelegate.swift ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ ├── Contents.json │ ├── images │ │ ├── Contents.json │ │ ├── _SlamDunkIMG02.imageset │ │ │ ├── Contents.json │ │ │ └── _SlamDunkIMG02.jpg │ │ ├── picker_cancel.imageset │ │ │ ├── Contents.json │ │ │ ├── picker_cancel@2x.png │ │ │ └── picker_cancel@3x.png │ │ └── picker_submit.imageset │ │ │ ├── Contents.json │ │ │ ├── picker_submit@2x.png │ │ │ └── picker_submit@3x.png │ ├── share_imgs │ │ ├── Contents.json │ │ ├── sheet_Collection.imageset │ │ │ ├── Contents.json │ │ │ └── sheet_Collection@3x.png │ │ ├── sheet_Facebook.imageset │ │ │ ├── Contents.json │ │ │ └── sheet_Facebook@3x.png │ │ ├── sheet_Moments.imageset │ │ │ ├── Contents.json │ │ │ └── sheet_Moments@3x.png │ │ ├── sheet_Share.imageset │ │ │ ├── Contents.json │ │ │ └── sheet_Share@3x.png │ │ ├── sheet_qq.imageset │ │ │ ├── Contents.json │ │ │ └── sheet_qq@3x.png │ │ ├── sheet_qqbrowser.imageset │ │ │ ├── Contents.json │ │ │ └── sheet_qqbrowser@3x.png │ │ └── sheet_qzone.imageset │ │ │ ├── Contents.json │ │ │ └── sheet_qzone@3x.png │ ├── side_imgs │ │ ├── Contents.json │ │ ├── Greenpeppers.imageset │ │ │ ├── Contents.json │ │ │ └── Greenpeppers.png │ │ ├── apple.imageset │ │ │ ├── Contents.json │ │ │ └── apple.png │ │ ├── banana.imageset │ │ │ ├── Contents.json │ │ │ └── banana.png │ │ ├── mango.imageset │ │ │ ├── Contents.json │ │ │ └── mango.png │ │ ├── orange.imageset │ │ │ ├── Contents.json │ │ │ └── orange.png │ │ ├── pineapple.imageset │ │ │ ├── Contents.json │ │ │ └── pineapple.png │ │ └── strawberry.imageset │ │ │ ├── Contents.json │ │ │ └── strawberry.png │ ├── sina_imgs │ │ ├── Contents.json │ │ ├── sina_关闭.imageset │ │ │ ├── Contents.json │ │ │ └── sina_关闭.png │ │ ├── sina_商品.imageset │ │ │ ├── Contents.json │ │ │ └── sina_商品.png │ │ ├── sina_头条文章.imageset │ │ │ ├── Contents.json │ │ │ └── sina_头条文章.png │ │ ├── sina_好友圈.imageset │ │ │ ├── Contents.json │ │ │ └── sina_好友圈.png │ │ ├── sina_文字.imageset │ │ │ ├── Contents.json │ │ │ └── sina_文字.png │ │ ├── sina_更多.imageset │ │ │ ├── Contents.json │ │ │ └── sina_更多.png │ │ ├── sina_点评.imageset │ │ │ ├── Contents.json │ │ │ └── sina_点评.png │ │ ├── sina_照片视频.imageset │ │ │ ├── Contents.json │ │ │ └── sina_照片视频.png │ │ ├── sina_直播.imageset │ │ │ ├── Contents.json │ │ │ └── sina_直播.png │ │ ├── sina_秒拍.imageset │ │ │ ├── Contents.json │ │ │ └── sina_秒拍.png │ │ ├── sina_签到.imageset │ │ │ ├── Contents.json │ │ │ └── sina_签到.png │ │ ├── sina_红包.imageset │ │ │ ├── Contents.json │ │ │ └── sina_红包.png │ │ ├── sina_返回.imageset │ │ │ ├── Contents.json │ │ │ └── sina_返回.png │ │ └── sina_音乐.imageset │ │ │ ├── Contents.json │ │ │ └── sina_音乐.png │ └── social_imgs │ │ ├── Contents.json │ │ ├── arrowup.imageset │ │ ├── Contents.json │ │ └── arrowup.png │ │ ├── social-github.imageset │ │ ├── Contents.json │ │ └── social-github.png │ │ ├── social-paypal.imageset │ │ ├── Contents.json │ │ └── social-paypal.png │ │ ├── social-pinterest.imageset │ │ ├── Contents.json │ │ └── social-pinterest.png │ │ ├── social-shopify.imageset │ │ ├── Contents.json │ │ └── social-shopify.png │ │ ├── social-skype.imageset │ │ ├── Contents.json │ │ └── social-skype.png │ │ ├── social-soundcloud.imageset │ │ ├── Contents.json │ │ └── social-soundcloud.png │ │ ├── social-spotify.imageset │ │ ├── Contents.json │ │ └── social-spotify.png │ │ ├── social-tumblr.imageset │ │ ├── Contents.json │ │ └── social-tumblr.png │ │ ├── social-twitter.imageset │ │ ├── Contents.json │ │ └── social-twitter.png │ │ ├── social-vimeo.imageset │ │ ├── Contents.json │ │ └── social-vimeo.png │ │ ├── social-whatsapp.imageset │ │ ├── Contents.json │ │ └── social-whatsapp.png │ │ ├── social-wordpress.imageset │ │ ├── Contents.json │ │ └── social-wordpress.png │ │ ├── social-yelp.imageset │ │ ├── Contents.json │ │ └── social-yelp.png │ │ └── social-youtube.imageset │ │ ├── Contents.json │ │ └── social-youtube.png ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── BaseNavigationController.swift ├── DataSource.swift ├── ExampleViewCell.swift ├── ExampleViewController.swift ├── Extensions │ ├── Array+PKExtensions.swift │ ├── CALayer+PKExtensions.swift │ ├── CGGeometry+PKExtensions.swift │ ├── Date+PKExtensions.swift │ ├── Double+PKExtensions.swift │ ├── NSAttributedString+PKExtensionsl.swift │ ├── String+PKExtensions.swift │ ├── Timer+PKExtensions.swift │ ├── UIApplication+PKExtensions.swift │ ├── UIBarButtonItem+PKExtensions.swift │ ├── UIButton+PKExtensions.swift │ ├── UIColor+PKExtensions.swift │ ├── UIControl+PKExtensions.swift │ ├── UIDevice+PKExtensions.swift │ ├── UIFont+PKExtensions.swift │ ├── UIGestureRecognizer+PKExtensions.swift │ ├── UIImage+PKExtensions.swift │ ├── UIKit+PKOverride.swift │ ├── UILabel+PKExtensions.swift │ ├── UIScreen+PKExtensions.swift │ ├── UIView+PKExtensions.swift │ └── UIViewController+PKExtensions.swift ├── Info.plist ├── OverlayController.swift ├── SceneDelegate.swift ├── ViewController.swift └── view │ ├── OverlayBalloonView.swift │ ├── OverlayPickerView.swift │ ├── OverlayPublishView.swift │ ├── OverlayShareView.swift │ ├── OverlaySidebarView.swift │ ├── OverlaySocialView.swift │ └── OverlayTextView.swift ├── OverlayControllerTests ├── Info.plist └── OverlayControllerTests.swift ├── OverlayControllerUITests ├── Info.plist └── OverlayControllerUITests.swift ├── README.md ├── Sources └── OverlayController.swift └── imgs ├── 1585744742654.jpg ├── zhpopupController05.gif └── zhpopupController11.gif /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snail-z/zhLoadingView/ac3112b830a2ebff786aa27426f8b6950379b64b/.DS_Store -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData/ 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata/ 19 | 20 | ## Other 21 | *.moved-aside 22 | *.xcuserstate 23 | 24 | ## Obj-C/Swift specific 25 | *.hmap 26 | *.ipa 27 | *.dSYM.zip 28 | *.dSYM 29 | 30 | # CocoaPods 31 | # 32 | # We recommend against adding the Pods directory to your .gitignore. However 33 | # you should judge for yourself, the pros and cons are mentioned at: 34 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 35 | # 36 | # Pods/ 37 | 38 | # Carthage 39 | # 40 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 41 | # Carthage/Checkouts 42 | 43 | Carthage/Build 44 | 45 | # fastlane 46 | # 47 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 48 | # screenshots whenever they are needed. 49 | # For more information about the recommended setup visit: 50 | # https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Gitignore.md 51 | 52 | fastlane/report.xml 53 | fastlane/screenshots 54 | 55 | #Code Injection 56 | # 57 | # After new code Injection tools there's a generated folder /iOSInjectionProject 58 | # https://github.com/johnno1962/injectionforxcode 59 | 60 | iOSInjectionProject/ 61 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2020 40460770@qq.com <40460770@qq.com> 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /OverlayController.podspec: -------------------------------------------------------------------------------- 1 | # 2 | # Be sure to run `pod lib lint OverlayController.podspec' to ensure this is a 3 | # valid spec before submitting. 4 | # 5 | # Any lines starting with a # are optional, but their use is encouraged 6 | # To learn more about a Podspec see https://guides.cocoapods.org/syntax/podspec.html 7 | # 8 | 9 | Pod::Spec.new do |s| 10 | s.name = 'OverlayController' 11 | s.version = '1.0.3' 12 | s.summary = 'OverlayController easily pop your custom view and provide optional transition animation.' 13 | 14 | # This description is used to generate tags and improve search results. 15 | # * Think: What does it do? Why did you write it? What is the focus? 16 | # * Try to keep it short, snappy and to the point. 17 | # * Write the description between the DESC delimiters below. 18 | # * Finally, don't worry about the indent, CocoaPods strips it! 19 | 20 | # s.description = <<-DESC 21 | #TODO: Add long description of the pod here. 22 | # DESC 23 | 24 | s.homepage = 'https://github.com/snail-z/OverlayController-Swift' 25 | # s.screenshots = 'www.example.com/screenshots_1', 'www.example.com/screenshots_2' 26 | s.license = { :type => 'MIT', :file => 'LICENSE' } 27 | s.author = { 'haozhang0770@163.com' => 'haozhang0770@163.com' } 28 | s.source = { :git => 'https://github.com/snail-z/zhLoadingView.git', :tag => s.version.to_s } 29 | # s.social_media_url = 'https://twitter.com/' 30 | 31 | s.swift_version = "5.0" 32 | s.ios.deployment_target = '10.0' 33 | s.requires_arc = true 34 | s.source_files = 'Sources/**' 35 | 36 | # s.resource_bundles = { 37 | # 'OverlayController' => ['OverlayController/Assets/*.png'] 38 | # } 39 | 40 | # s.public_header_files = 'Pod/Classes/**/*.h' 41 | # s.frameworks = 'UIKit', 'MapKit' 42 | # s.dependency 'AFNetworking', '~> 2.3' 43 | end 44 | -------------------------------------------------------------------------------- /OverlayController.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /OverlayController.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /OverlayController/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // OverlayController 4 | // 5 | // Created by ahong on 2020/2/26. 6 | // Copyright © 2020 zhanghao. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import CoreData 11 | 12 | @UIApplicationMain 13 | class AppDelegate: UIResponder, UIApplicationDelegate { 14 | 15 | var window: UIWindow? 16 | 17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 18 | // Override point for customization after application launch. 19 | return true 20 | } 21 | 22 | // MARK: UISceneSession Lifecycle 23 | 24 | @available(iOS 13.0, *) 25 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { 26 | // Called when a new scene session is being created. 27 | // Use this method to select a configuration to create the new scene with. 28 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) 29 | } 30 | 31 | @available(iOS 13.0, *) 32 | func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { 33 | // Called when the user discards a scene session. 34 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. 35 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return. 36 | } 37 | 38 | // MARK: - Core Data stack 39 | 40 | lazy var persistentContainer: NSPersistentContainer = { 41 | /* 42 | The persistent container for the application. This implementation 43 | creates and returns a container, having loaded the store for the 44 | application to it. This property is optional since there are legitimate 45 | error conditions that could cause the creation of the store to fail. 46 | */ 47 | let container = NSPersistentContainer(name: "AirTraffic_swift") 48 | container.loadPersistentStores(completionHandler: { (storeDescription, error) in 49 | if let error = error as NSError? { 50 | // Replace this implementation with code to handle the error appropriately. 51 | // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. 52 | 53 | /* 54 | Typical reasons for an error here include: 55 | * The parent directory does not exist, cannot be created, or disallows writing. 56 | * The persistent store is not accessible, due to permissions or data protection when the device is locked. 57 | * The device is out of space. 58 | * The store could not be migrated to the current model version. 59 | Check the error message to determine what the actual problem was. 60 | */ 61 | fatalError("Unresolved error \(error), \(error.userInfo)") 62 | } 63 | }) 64 | return container 65 | }() 66 | 67 | // MARK: - Core Data Saving support 68 | 69 | func saveContext () { 70 | let context = persistentContainer.viewContext 71 | if context.hasChanges { 72 | do { 73 | try context.save() 74 | } catch { 75 | // Replace this implementation with code to handle the error appropriately. 76 | // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. 77 | let nserror = error as NSError 78 | fatalError("Unresolved error \(nserror), \(nserror.userInfo)") 79 | } 80 | } 81 | } 82 | 83 | } 84 | 85 | -------------------------------------------------------------------------------- /OverlayController/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } -------------------------------------------------------------------------------- /OverlayController/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /OverlayController/Assets.xcassets/images/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /OverlayController/Assets.xcassets/images/_SlamDunkIMG02.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "_SlamDunkIMG02.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /OverlayController/Assets.xcassets/images/_SlamDunkIMG02.imageset/_SlamDunkIMG02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snail-z/zhLoadingView/ac3112b830a2ebff786aa27426f8b6950379b64b/OverlayController/Assets.xcassets/images/_SlamDunkIMG02.imageset/_SlamDunkIMG02.jpg -------------------------------------------------------------------------------- /OverlayController/Assets.xcassets/images/picker_cancel.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "picker_cancel@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "picker_cancel@3x.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /OverlayController/Assets.xcassets/images/picker_cancel.imageset/picker_cancel@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snail-z/zhLoadingView/ac3112b830a2ebff786aa27426f8b6950379b64b/OverlayController/Assets.xcassets/images/picker_cancel.imageset/picker_cancel@2x.png -------------------------------------------------------------------------------- /OverlayController/Assets.xcassets/images/picker_cancel.imageset/picker_cancel@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snail-z/zhLoadingView/ac3112b830a2ebff786aa27426f8b6950379b64b/OverlayController/Assets.xcassets/images/picker_cancel.imageset/picker_cancel@3x.png -------------------------------------------------------------------------------- /OverlayController/Assets.xcassets/images/picker_submit.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "picker_submit@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "picker_submit@3x.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /OverlayController/Assets.xcassets/images/picker_submit.imageset/picker_submit@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snail-z/zhLoadingView/ac3112b830a2ebff786aa27426f8b6950379b64b/OverlayController/Assets.xcassets/images/picker_submit.imageset/picker_submit@2x.png -------------------------------------------------------------------------------- /OverlayController/Assets.xcassets/images/picker_submit.imageset/picker_submit@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snail-z/zhLoadingView/ac3112b830a2ebff786aa27426f8b6950379b64b/OverlayController/Assets.xcassets/images/picker_submit.imageset/picker_submit@3x.png -------------------------------------------------------------------------------- /OverlayController/Assets.xcassets/share_imgs/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /OverlayController/Assets.xcassets/share_imgs/sheet_Collection.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x" 10 | }, 11 | { 12 | "idiom" : "universal", 13 | "filename" : "sheet_Collection@3x.png", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /OverlayController/Assets.xcassets/share_imgs/sheet_Collection.imageset/sheet_Collection@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snail-z/zhLoadingView/ac3112b830a2ebff786aa27426f8b6950379b64b/OverlayController/Assets.xcassets/share_imgs/sheet_Collection.imageset/sheet_Collection@3x.png -------------------------------------------------------------------------------- /OverlayController/Assets.xcassets/share_imgs/sheet_Facebook.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x" 10 | }, 11 | { 12 | "idiom" : "universal", 13 | "filename" : "sheet_Facebook@3x.png", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /OverlayController/Assets.xcassets/share_imgs/sheet_Facebook.imageset/sheet_Facebook@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snail-z/zhLoadingView/ac3112b830a2ebff786aa27426f8b6950379b64b/OverlayController/Assets.xcassets/share_imgs/sheet_Facebook.imageset/sheet_Facebook@3x.png -------------------------------------------------------------------------------- /OverlayController/Assets.xcassets/share_imgs/sheet_Moments.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x" 10 | }, 11 | { 12 | "idiom" : "universal", 13 | "filename" : "sheet_Moments@3x.png", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /OverlayController/Assets.xcassets/share_imgs/sheet_Moments.imageset/sheet_Moments@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snail-z/zhLoadingView/ac3112b830a2ebff786aa27426f8b6950379b64b/OverlayController/Assets.xcassets/share_imgs/sheet_Moments.imageset/sheet_Moments@3x.png -------------------------------------------------------------------------------- /OverlayController/Assets.xcassets/share_imgs/sheet_Share.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x" 10 | }, 11 | { 12 | "idiom" : "universal", 13 | "filename" : "sheet_Share@3x.png", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /OverlayController/Assets.xcassets/share_imgs/sheet_Share.imageset/sheet_Share@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snail-z/zhLoadingView/ac3112b830a2ebff786aa27426f8b6950379b64b/OverlayController/Assets.xcassets/share_imgs/sheet_Share.imageset/sheet_Share@3x.png -------------------------------------------------------------------------------- /OverlayController/Assets.xcassets/share_imgs/sheet_qq.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x" 10 | }, 11 | { 12 | "idiom" : "universal", 13 | "filename" : "sheet_qq@3x.png", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /OverlayController/Assets.xcassets/share_imgs/sheet_qq.imageset/sheet_qq@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snail-z/zhLoadingView/ac3112b830a2ebff786aa27426f8b6950379b64b/OverlayController/Assets.xcassets/share_imgs/sheet_qq.imageset/sheet_qq@3x.png -------------------------------------------------------------------------------- /OverlayController/Assets.xcassets/share_imgs/sheet_qqbrowser.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x" 10 | }, 11 | { 12 | "idiom" : "universal", 13 | "filename" : "sheet_qqbrowser@3x.png", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /OverlayController/Assets.xcassets/share_imgs/sheet_qqbrowser.imageset/sheet_qqbrowser@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snail-z/zhLoadingView/ac3112b830a2ebff786aa27426f8b6950379b64b/OverlayController/Assets.xcassets/share_imgs/sheet_qqbrowser.imageset/sheet_qqbrowser@3x.png -------------------------------------------------------------------------------- /OverlayController/Assets.xcassets/share_imgs/sheet_qzone.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x" 10 | }, 11 | { 12 | "idiom" : "universal", 13 | "filename" : "sheet_qzone@3x.png", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /OverlayController/Assets.xcassets/share_imgs/sheet_qzone.imageset/sheet_qzone@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snail-z/zhLoadingView/ac3112b830a2ebff786aa27426f8b6950379b64b/OverlayController/Assets.xcassets/share_imgs/sheet_qzone.imageset/sheet_qzone@3x.png -------------------------------------------------------------------------------- /OverlayController/Assets.xcassets/side_imgs/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /OverlayController/Assets.xcassets/side_imgs/Greenpeppers.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "Greenpeppers.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /OverlayController/Assets.xcassets/side_imgs/Greenpeppers.imageset/Greenpeppers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snail-z/zhLoadingView/ac3112b830a2ebff786aa27426f8b6950379b64b/OverlayController/Assets.xcassets/side_imgs/Greenpeppers.imageset/Greenpeppers.png -------------------------------------------------------------------------------- /OverlayController/Assets.xcassets/side_imgs/apple.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "apple.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /OverlayController/Assets.xcassets/side_imgs/apple.imageset/apple.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snail-z/zhLoadingView/ac3112b830a2ebff786aa27426f8b6950379b64b/OverlayController/Assets.xcassets/side_imgs/apple.imageset/apple.png -------------------------------------------------------------------------------- /OverlayController/Assets.xcassets/side_imgs/banana.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "banana.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /OverlayController/Assets.xcassets/side_imgs/banana.imageset/banana.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snail-z/zhLoadingView/ac3112b830a2ebff786aa27426f8b6950379b64b/OverlayController/Assets.xcassets/side_imgs/banana.imageset/banana.png -------------------------------------------------------------------------------- /OverlayController/Assets.xcassets/side_imgs/mango.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "mango.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /OverlayController/Assets.xcassets/side_imgs/mango.imageset/mango.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snail-z/zhLoadingView/ac3112b830a2ebff786aa27426f8b6950379b64b/OverlayController/Assets.xcassets/side_imgs/mango.imageset/mango.png -------------------------------------------------------------------------------- /OverlayController/Assets.xcassets/side_imgs/orange.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "orange.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /OverlayController/Assets.xcassets/side_imgs/orange.imageset/orange.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snail-z/zhLoadingView/ac3112b830a2ebff786aa27426f8b6950379b64b/OverlayController/Assets.xcassets/side_imgs/orange.imageset/orange.png -------------------------------------------------------------------------------- /OverlayController/Assets.xcassets/side_imgs/pineapple.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "pineapple.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /OverlayController/Assets.xcassets/side_imgs/pineapple.imageset/pineapple.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snail-z/zhLoadingView/ac3112b830a2ebff786aa27426f8b6950379b64b/OverlayController/Assets.xcassets/side_imgs/pineapple.imageset/pineapple.png -------------------------------------------------------------------------------- /OverlayController/Assets.xcassets/side_imgs/strawberry.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "strawberry.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /OverlayController/Assets.xcassets/side_imgs/strawberry.imageset/strawberry.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snail-z/zhLoadingView/ac3112b830a2ebff786aa27426f8b6950379b64b/OverlayController/Assets.xcassets/side_imgs/strawberry.imageset/strawberry.png -------------------------------------------------------------------------------- /OverlayController/Assets.xcassets/sina_imgs/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /OverlayController/Assets.xcassets/sina_imgs/sina_关闭.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "sina_关闭.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /OverlayController/Assets.xcassets/sina_imgs/sina_关闭.imageset/sina_关闭.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snail-z/zhLoadingView/ac3112b830a2ebff786aa27426f8b6950379b64b/OverlayController/Assets.xcassets/sina_imgs/sina_关闭.imageset/sina_关闭.png -------------------------------------------------------------------------------- /OverlayController/Assets.xcassets/sina_imgs/sina_商品.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "sina_商品.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /OverlayController/Assets.xcassets/sina_imgs/sina_商品.imageset/sina_商品.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snail-z/zhLoadingView/ac3112b830a2ebff786aa27426f8b6950379b64b/OverlayController/Assets.xcassets/sina_imgs/sina_商品.imageset/sina_商品.png -------------------------------------------------------------------------------- /OverlayController/Assets.xcassets/sina_imgs/sina_头条文章.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "sina_头条文章.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /OverlayController/Assets.xcassets/sina_imgs/sina_头条文章.imageset/sina_头条文章.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snail-z/zhLoadingView/ac3112b830a2ebff786aa27426f8b6950379b64b/OverlayController/Assets.xcassets/sina_imgs/sina_头条文章.imageset/sina_头条文章.png -------------------------------------------------------------------------------- /OverlayController/Assets.xcassets/sina_imgs/sina_好友圈.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "sina_好友圈.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /OverlayController/Assets.xcassets/sina_imgs/sina_好友圈.imageset/sina_好友圈.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snail-z/zhLoadingView/ac3112b830a2ebff786aa27426f8b6950379b64b/OverlayController/Assets.xcassets/sina_imgs/sina_好友圈.imageset/sina_好友圈.png -------------------------------------------------------------------------------- /OverlayController/Assets.xcassets/sina_imgs/sina_文字.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "sina_文字.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /OverlayController/Assets.xcassets/sina_imgs/sina_文字.imageset/sina_文字.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snail-z/zhLoadingView/ac3112b830a2ebff786aa27426f8b6950379b64b/OverlayController/Assets.xcassets/sina_imgs/sina_文字.imageset/sina_文字.png -------------------------------------------------------------------------------- /OverlayController/Assets.xcassets/sina_imgs/sina_更多.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "sina_更多.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /OverlayController/Assets.xcassets/sina_imgs/sina_更多.imageset/sina_更多.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snail-z/zhLoadingView/ac3112b830a2ebff786aa27426f8b6950379b64b/OverlayController/Assets.xcassets/sina_imgs/sina_更多.imageset/sina_更多.png -------------------------------------------------------------------------------- /OverlayController/Assets.xcassets/sina_imgs/sina_点评.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "sina_点评.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /OverlayController/Assets.xcassets/sina_imgs/sina_点评.imageset/sina_点评.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snail-z/zhLoadingView/ac3112b830a2ebff786aa27426f8b6950379b64b/OverlayController/Assets.xcassets/sina_imgs/sina_点评.imageset/sina_点评.png -------------------------------------------------------------------------------- /OverlayController/Assets.xcassets/sina_imgs/sina_照片视频.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "sina_照片视频.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /OverlayController/Assets.xcassets/sina_imgs/sina_照片视频.imageset/sina_照片视频.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snail-z/zhLoadingView/ac3112b830a2ebff786aa27426f8b6950379b64b/OverlayController/Assets.xcassets/sina_imgs/sina_照片视频.imageset/sina_照片视频.png -------------------------------------------------------------------------------- /OverlayController/Assets.xcassets/sina_imgs/sina_直播.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "sina_直播.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /OverlayController/Assets.xcassets/sina_imgs/sina_直播.imageset/sina_直播.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snail-z/zhLoadingView/ac3112b830a2ebff786aa27426f8b6950379b64b/OverlayController/Assets.xcassets/sina_imgs/sina_直播.imageset/sina_直播.png -------------------------------------------------------------------------------- /OverlayController/Assets.xcassets/sina_imgs/sina_秒拍.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "sina_秒拍.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /OverlayController/Assets.xcassets/sina_imgs/sina_秒拍.imageset/sina_秒拍.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snail-z/zhLoadingView/ac3112b830a2ebff786aa27426f8b6950379b64b/OverlayController/Assets.xcassets/sina_imgs/sina_秒拍.imageset/sina_秒拍.png -------------------------------------------------------------------------------- /OverlayController/Assets.xcassets/sina_imgs/sina_签到.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "sina_签到.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /OverlayController/Assets.xcassets/sina_imgs/sina_签到.imageset/sina_签到.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snail-z/zhLoadingView/ac3112b830a2ebff786aa27426f8b6950379b64b/OverlayController/Assets.xcassets/sina_imgs/sina_签到.imageset/sina_签到.png -------------------------------------------------------------------------------- /OverlayController/Assets.xcassets/sina_imgs/sina_红包.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "sina_红包.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /OverlayController/Assets.xcassets/sina_imgs/sina_红包.imageset/sina_红包.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snail-z/zhLoadingView/ac3112b830a2ebff786aa27426f8b6950379b64b/OverlayController/Assets.xcassets/sina_imgs/sina_红包.imageset/sina_红包.png -------------------------------------------------------------------------------- /OverlayController/Assets.xcassets/sina_imgs/sina_返回.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "sina_返回.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /OverlayController/Assets.xcassets/sina_imgs/sina_返回.imageset/sina_返回.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snail-z/zhLoadingView/ac3112b830a2ebff786aa27426f8b6950379b64b/OverlayController/Assets.xcassets/sina_imgs/sina_返回.imageset/sina_返回.png -------------------------------------------------------------------------------- /OverlayController/Assets.xcassets/sina_imgs/sina_音乐.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "sina_音乐.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /OverlayController/Assets.xcassets/sina_imgs/sina_音乐.imageset/sina_音乐.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snail-z/zhLoadingView/ac3112b830a2ebff786aa27426f8b6950379b64b/OverlayController/Assets.xcassets/sina_imgs/sina_音乐.imageset/sina_音乐.png -------------------------------------------------------------------------------- /OverlayController/Assets.xcassets/social_imgs/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /OverlayController/Assets.xcassets/social_imgs/arrowup.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "arrowup.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /OverlayController/Assets.xcassets/social_imgs/arrowup.imageset/arrowup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snail-z/zhLoadingView/ac3112b830a2ebff786aa27426f8b6950379b64b/OverlayController/Assets.xcassets/social_imgs/arrowup.imageset/arrowup.png -------------------------------------------------------------------------------- /OverlayController/Assets.xcassets/social_imgs/social-github.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "social-github.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /OverlayController/Assets.xcassets/social_imgs/social-github.imageset/social-github.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snail-z/zhLoadingView/ac3112b830a2ebff786aa27426f8b6950379b64b/OverlayController/Assets.xcassets/social_imgs/social-github.imageset/social-github.png -------------------------------------------------------------------------------- /OverlayController/Assets.xcassets/social_imgs/social-paypal.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "social-paypal.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /OverlayController/Assets.xcassets/social_imgs/social-paypal.imageset/social-paypal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snail-z/zhLoadingView/ac3112b830a2ebff786aa27426f8b6950379b64b/OverlayController/Assets.xcassets/social_imgs/social-paypal.imageset/social-paypal.png -------------------------------------------------------------------------------- /OverlayController/Assets.xcassets/social_imgs/social-pinterest.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "social-pinterest.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /OverlayController/Assets.xcassets/social_imgs/social-pinterest.imageset/social-pinterest.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snail-z/zhLoadingView/ac3112b830a2ebff786aa27426f8b6950379b64b/OverlayController/Assets.xcassets/social_imgs/social-pinterest.imageset/social-pinterest.png -------------------------------------------------------------------------------- /OverlayController/Assets.xcassets/social_imgs/social-shopify.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "social-shopify.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /OverlayController/Assets.xcassets/social_imgs/social-shopify.imageset/social-shopify.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snail-z/zhLoadingView/ac3112b830a2ebff786aa27426f8b6950379b64b/OverlayController/Assets.xcassets/social_imgs/social-shopify.imageset/social-shopify.png -------------------------------------------------------------------------------- /OverlayController/Assets.xcassets/social_imgs/social-skype.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "social-skype.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /OverlayController/Assets.xcassets/social_imgs/social-skype.imageset/social-skype.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snail-z/zhLoadingView/ac3112b830a2ebff786aa27426f8b6950379b64b/OverlayController/Assets.xcassets/social_imgs/social-skype.imageset/social-skype.png -------------------------------------------------------------------------------- /OverlayController/Assets.xcassets/social_imgs/social-soundcloud.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "social-soundcloud.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /OverlayController/Assets.xcassets/social_imgs/social-soundcloud.imageset/social-soundcloud.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snail-z/zhLoadingView/ac3112b830a2ebff786aa27426f8b6950379b64b/OverlayController/Assets.xcassets/social_imgs/social-soundcloud.imageset/social-soundcloud.png -------------------------------------------------------------------------------- /OverlayController/Assets.xcassets/social_imgs/social-spotify.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "social-spotify.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /OverlayController/Assets.xcassets/social_imgs/social-spotify.imageset/social-spotify.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snail-z/zhLoadingView/ac3112b830a2ebff786aa27426f8b6950379b64b/OverlayController/Assets.xcassets/social_imgs/social-spotify.imageset/social-spotify.png -------------------------------------------------------------------------------- /OverlayController/Assets.xcassets/social_imgs/social-tumblr.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "social-tumblr.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /OverlayController/Assets.xcassets/social_imgs/social-tumblr.imageset/social-tumblr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snail-z/zhLoadingView/ac3112b830a2ebff786aa27426f8b6950379b64b/OverlayController/Assets.xcassets/social_imgs/social-tumblr.imageset/social-tumblr.png -------------------------------------------------------------------------------- /OverlayController/Assets.xcassets/social_imgs/social-twitter.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "social-twitter.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /OverlayController/Assets.xcassets/social_imgs/social-twitter.imageset/social-twitter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snail-z/zhLoadingView/ac3112b830a2ebff786aa27426f8b6950379b64b/OverlayController/Assets.xcassets/social_imgs/social-twitter.imageset/social-twitter.png -------------------------------------------------------------------------------- /OverlayController/Assets.xcassets/social_imgs/social-vimeo.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "social-vimeo.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /OverlayController/Assets.xcassets/social_imgs/social-vimeo.imageset/social-vimeo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snail-z/zhLoadingView/ac3112b830a2ebff786aa27426f8b6950379b64b/OverlayController/Assets.xcassets/social_imgs/social-vimeo.imageset/social-vimeo.png -------------------------------------------------------------------------------- /OverlayController/Assets.xcassets/social_imgs/social-whatsapp.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "social-whatsapp.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /OverlayController/Assets.xcassets/social_imgs/social-whatsapp.imageset/social-whatsapp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snail-z/zhLoadingView/ac3112b830a2ebff786aa27426f8b6950379b64b/OverlayController/Assets.xcassets/social_imgs/social-whatsapp.imageset/social-whatsapp.png -------------------------------------------------------------------------------- /OverlayController/Assets.xcassets/social_imgs/social-wordpress.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "social-wordpress.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /OverlayController/Assets.xcassets/social_imgs/social-wordpress.imageset/social-wordpress.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snail-z/zhLoadingView/ac3112b830a2ebff786aa27426f8b6950379b64b/OverlayController/Assets.xcassets/social_imgs/social-wordpress.imageset/social-wordpress.png -------------------------------------------------------------------------------- /OverlayController/Assets.xcassets/social_imgs/social-yelp.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "social-yelp.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /OverlayController/Assets.xcassets/social_imgs/social-yelp.imageset/social-yelp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snail-z/zhLoadingView/ac3112b830a2ebff786aa27426f8b6950379b64b/OverlayController/Assets.xcassets/social_imgs/social-yelp.imageset/social-yelp.png -------------------------------------------------------------------------------- /OverlayController/Assets.xcassets/social_imgs/social-youtube.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "social-youtube.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /OverlayController/Assets.xcassets/social_imgs/social-youtube.imageset/social-youtube.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snail-z/zhLoadingView/ac3112b830a2ebff786aa27426f8b6950379b64b/OverlayController/Assets.xcassets/social_imgs/social-youtube.imageset/social-youtube.png -------------------------------------------------------------------------------- /OverlayController/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 | -------------------------------------------------------------------------------- /OverlayController/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 | -------------------------------------------------------------------------------- /OverlayController/BaseNavigationController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BaseNavigationController.swift 3 | // OverlayController 4 | // 5 | // Created by ahong on 2020/3/2. 6 | // Copyright © 2020 zhanghao. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class BaseNavigationController: UINavigationController { 12 | 13 | override var childForStatusBarStyle: UIViewController? { 14 | return topViewController 15 | } 16 | 17 | override var childForStatusBarHidden: UIViewController? { 18 | return topViewController 19 | } 20 | 21 | override func viewDidLoad() { 22 | super.viewDidLoad() 23 | // Do any additional setup after loading the view. 24 | } 25 | 26 | 27 | /* 28 | // MARK: - Navigation 29 | 30 | // In a storyboard-based application, you will often want to do a little preparation before navigation 31 | override func prepare(for segue: UIStoryboardSegue, sender: Any?) { 32 | // Get the new view controller using segue.destination. 33 | // Pass the selected object to the new view controller. 34 | } 35 | */ 36 | 37 | } 38 | -------------------------------------------------------------------------------- /OverlayController/DataSource.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DataSource.swift 3 | // OverlayController 4 | // 5 | // Created by ahong on 2020/3/1. 6 | // Copyright © 2020 zhanghao. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | public struct DataSource { 12 | 13 | static var shareItems: [[OverlayShareView.Data]] { 14 | let data1 = OverlayShareView.Data(image: UIImage(named: "sheet_Share"), title: "发送给朋友") 15 | let data2 = OverlayShareView.Data(image: UIImage(named: "sheet_Moments"), title: "分享到朋友圈") 16 | let data3 = OverlayShareView.Data(image: UIImage(named: "sheet_qq"), title: "分享到\n手机QQ") 17 | let data5 = OverlayShareView.Data(image: UIImage(named: "sheet_Moments"), title: "分享到\n企业微信") 18 | let data6 = OverlayShareView.Data(image: UIImage(named: "sheet_Moments"), title: "分享到\n企业微信") 19 | return [[data1, data6, data2, data3, data5, data2], [data1, data2, data3, data1, data6]] 20 | } 21 | 22 | static var socialItems: [[OverlaySocialView.Data]] { 23 | let data1 = OverlaySocialView.Data.init(image: UIImage(named: "social-github"), title: "github") 24 | let data2 = OverlaySocialView.Data.init(image: UIImage(named: "social-paypal"), title: "paypal") 25 | let data3 = OverlaySocialView.Data.init(image: UIImage(named: "social-pinterest"), title: "pinter") 26 | let data4 = OverlaySocialView.Data(image: UIImage(named: "social-soundcloud"), title: "sdcloud") 27 | let data5 = OverlaySocialView.Data(image: UIImage(named: "social-shopify"), title: "shopify") 28 | let data6 = OverlaySocialView.Data(image: UIImage(named: "social-skype"), title: "skype") 29 | 30 | let item0 = OverlaySocialView.Data(image: UIImage(named: "social-youtube"), title: "youtube") 31 | let item1 = OverlaySocialView.Data(image: UIImage(named: "social-spotify"), title: "spotify") 32 | let item2 = OverlaySocialView.Data(image: UIImage(named: "social-tumblr"), title: "tumblr") 33 | let item3 = OverlaySocialView.Data(image: UIImage(named: "social-twitter"), title: "twitter") 34 | let item4 = OverlaySocialView.Data(image: UIImage(named: "social-vimeo"), title: "vimeo") 35 | let item5 = OverlaySocialView.Data(image: UIImage(named: "social-whatsapp"), title: "whatsapp") 36 | let item6 = OverlaySocialView.Data(image: UIImage(named: "social-wordpress"), title: "wordpress") 37 | let item7 = OverlaySocialView.Data(image: UIImage(named: "social-yelp"), title: "yelp") 38 | return [[data1, data2, data3, data5, data4, data6], 39 | [item0, item1, item2, item3, item4, item5, item6, item7, item2, item4, data3, data5, data4, data6]] 40 | } 41 | 42 | static var balloonItems: [OverlayBalloonView.Data] { 43 | let titles = ["文字", "照片视频", "头条文章", "红包", "直播", "点评", "好友圈", "更多", "音乐", "商品", "签到", "秒拍"] 44 | return titles.map { (name) -> OverlayBalloonView.Data in 45 | let img = UIImage(named: "sina_".appending(name)) 46 | let data = OverlayBalloonView.Data.init(image: img, title: name) 47 | return data 48 | } 49 | } 50 | 51 | static var sidebarItems: [OverlaySidebarView.Data] { 52 | let item1 = OverlaySidebarView.Data(image: UIImage(named: "apple"), title: "Apple pie") 53 | let item2 = OverlaySidebarView.Data(image: UIImage(named: "banana"), title: "Banana bread") 54 | let item3 = OverlaySidebarView.Data(image: UIImage(named: "pineapple"), title: "Pineapple pie") 55 | let item4 = OverlaySidebarView.Data(image: UIImage(named: "strawberry"), title: "Strawberry pie") 56 | let item5 = OverlaySidebarView.Data(image: UIImage(named: "mango"), title: "Mango pie") 57 | let item6 = OverlaySidebarView.Data(image: UIImage(named: "orange"), title: "Orange juice") 58 | let item7 = OverlaySidebarView.Data(image: UIImage(named: "Greenpeppers"), title: "Green peppers") 59 | return [item1, item2, item3, item4, item5, item6, item7] 60 | } 61 | } 62 | 63 | public struct OverlayPickerData { 64 | 65 | enum TimeType: Int { 66 | case year, month, day, hour, minute 67 | } 68 | 69 | func format(_ value: Int, type: TimeType) -> String { 70 | switch type { 71 | case .year: return String(value) 72 | case .month: return String(format: "%.d", value) 73 | case .day: return String(format: "%.d", value) 74 | case .hour: return String(format: "%.2d", value) 75 | case .minute: return String(format: "%.2d", value) 76 | } 77 | } 78 | 79 | struct Item { 80 | var title: String 81 | var rawValue: Int 82 | } 83 | 84 | private(set) lazy var years: [Item] = { 85 | var mud = [Item]() 86 | for idx in 1970...2099 { 87 | let value = format(idx, type: .year) 88 | mud.append(Item(title: value + "年", rawValue: idx)) 89 | } 90 | return mud 91 | }() 92 | 93 | private(set) lazy var months: [Item] = { 94 | var mud = [Item]() 95 | for idx in 1...12 { 96 | let value = format(idx, type: .month) 97 | mud.append(Item(title: value + "月", rawValue: idx)) 98 | } 99 | return mud 100 | }() 101 | 102 | private(set) lazy var hours: [Item] = { 103 | var mud = [Item]() 104 | for idx in 0..<24 { 105 | let value = format(idx, type: .hour) 106 | mud.append(Item(title: value + "点", rawValue: idx)) 107 | } 108 | return mud 109 | }() 110 | 111 | private(set) lazy var minutes: [Item] = { 112 | var mud = [Item]() 113 | for idx in 0...59 { 114 | let value = format(idx, type: .minute) 115 | mud.append(Item(title: value + "分", rawValue: idx)) 116 | } 117 | return mud 118 | }() 119 | 120 | private(set) var days = [Item]() 121 | 122 | mutating func make(days max: Int) { 123 | var mud = [Item]() 124 | for idx in 1...max { 125 | let value = format(idx, type: .day) 126 | mud.append(Item(title: value + "日", rawValue: idx)) 127 | } 128 | self.days = mud 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /OverlayController/ExampleViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ExampleViewCell.swift 3 | // OverlayController 4 | // 5 | // Created by ahong on 2020/2/27. 6 | // Copyright © 2020 zhanghao. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ExampleViewCell: UITableViewCell { 12 | 13 | override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { 14 | super.init(style: style, reuseIdentifier: reuseIdentifier) 15 | backgroundColor = .clear 16 | selectionStyle = .none 17 | setup() 18 | } 19 | 20 | required init?(coder: NSCoder) { 21 | fatalError("init(coder:) has not been implemented") 22 | } 23 | 24 | var button: UIButton! 25 | func setup() { 26 | button = UIButton(type: .custom) 27 | button.layer.masksToBounds = true 28 | button.layer.cornerRadius = 2 29 | button.setTitleColor(.black, for: .normal) 30 | button.titleLabel?.font = UIFont.pk.fontName(.gillSans, style: .semiBoldItalic, size: 17) 31 | addSubview(button) 32 | } 33 | 34 | override func layoutSubviews() { 35 | super.layoutSubviews() 36 | button.frame = CGRect(origin: .zero, size: CGSize(width: 125, height: 40)) 37 | button.center = CGPoint(x: bounds.width / 2, y: bounds.height / 2) 38 | button.pk.addGradient(colors: [UIColor.white.withAlphaComponent(1), UIColor.lightGray.withAlphaComponent(0.7)], direction: .leftTopToRightBottom) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /OverlayController/Extensions/Array+PKExtensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PKExtensions.swift 3 | // PKExtensions 4 | // 5 | // Created by zhanghao on 2020/2/23. 6 | // Copyright © 2020 zhanghao. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | public extension PKArrayExtensions { 12 | 13 | /// 获取数组中间元素下标 (数组元素个数为奇数时准确唯一) 14 | var midIndex: Int { base.endIndex / 2 } 15 | 16 | /// 获取数组中一个随机下标 17 | var randomIndex: Int { Int.random(in: 0.. Element? { 21 | guard index >= 0, index < base.count else { return nil } 22 | return base[index] 23 | } 24 | 25 | /// 获取指定索引对应的元素,索引不存在则返回默认值 26 | func element(safe index: Int, default element: Element) -> Element { 27 | guard index >= 0, index < base.count else { return element } 28 | return base[index] 29 | } 30 | 31 | /// 根据闭包返回的某个条件,查找数组内的最大最小值 32 | /// 33 | /// let array: [UIButton] = [...] 34 | /// let value = array.pk.maximin({ $0.tag }) 35 | /// // print(value.max, value.min) 36 | /// 37 | func maximin(_ block: (_ sender: Element) -> T) -> (max: T, min: T)? { 38 | guard !base.isEmpty else { return nil } 39 | var minValue = block(base[0]), maxValue = block(base[0]) 40 | let lastIndex = base.count - 1 41 | for index in stride(from: 0, to: lastIndex, by: 2) { 42 | let one = block(base[index]), two = block(base[index + 1]) 43 | let maxTemp = Swift.max(one, two) 44 | let minTemp = Swift.min(one, two) 45 | if maxTemp > maxValue { maxValue = maxTemp } 46 | if minTemp < minValue { minValue = minTemp } 47 | } 48 | let lastValue = block(base[lastIndex]) 49 | if lastValue > maxValue { maxValue = lastValue } 50 | if lastValue < minValue { minValue = lastValue } 51 | return (maxValue, minValue) 52 | } 53 | } 54 | 55 | public extension Collection { 56 | 57 | /// 返回指定索引对应的元素,若索引越界则返回nil 58 | subscript (safe index: Index) -> Iterator.Element? { 59 | return indices.contains(index) ? self[index] : nil 60 | } 61 | } 62 | 63 | public struct PKArrayExtensions { 64 | fileprivate static var Base: Array.Type { Array.self } 65 | fileprivate var base: Array 66 | fileprivate init(_ base: Array) { self.base = base } 67 | } 68 | 69 | public extension Array { 70 | var pk: PKArrayExtensions { PKArrayExtensions(self) } 71 | static var pk: PKArrayExtensions.Type { PKArrayExtensions.self } 72 | } 73 | -------------------------------------------------------------------------------- /OverlayController/Extensions/CALayer+PKExtensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CALayer+PKExtensions.swift 3 | // PKExtensions 4 | // 5 | // Created by zhanghao on 2020/2/24. 6 | // Copyright © 2020 zhanghao. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | public extension PKLayerExtensions { 12 | 13 | /// 删除layer的所有子图层 14 | func removeAllSublayers() { 15 | while base.sublayers?.count ?? 0 > 0 { 16 | base.sublayers?.last?.removeFromSuperlayer() 17 | } 18 | } 19 | 20 | /// 返回对当前Layer的截图 21 | func screenshots() -> UIImage? { 22 | guard base.bounds.width > 0, base.bounds.height > 0 else { return nil } 23 | guard let context = UIGraphicsGetCurrentContext() else { return nil } 24 | UIGraphicsBeginImageContextWithOptions(base.bounds.size, false, UIScreen.main.scale) 25 | base.render(in: context) 26 | let image = UIGraphicsGetImageFromCurrentImageContext() 27 | UIGraphicsEndImageContext() 28 | return image 29 | } 30 | 31 | /// 为layer添加fade动画,当图层内容变化时将以淡入淡出动画使内容渐变 32 | func fadeContent(_ duration: TimeInterval = 0.25, curve: CAMediaTimingFunctionName) { 33 | let animation = CATransition() 34 | animation.timingFunction = CAMediaTimingFunction(name: curve) 35 | animation.type = .fade 36 | animation.duration = duration 37 | base.add(animation, forKey: "_pkExtensions.anim.fade") 38 | } 39 | } 40 | 41 | public struct PKLayerExtensions { 42 | fileprivate static var Base: CALayer.Type { CALayer.self } 43 | fileprivate var base: CALayer 44 | fileprivate init(_ base: CALayer) { self.base = base } 45 | } 46 | 47 | public extension CALayer { 48 | var pk: PKLayerExtensions { PKLayerExtensions(self) } 49 | static var pk: PKLayerExtensions.Type { PKLayerExtensions.self } 50 | } 51 | 52 | public extension CALayer { 53 | 54 | var left: CGFloat { 55 | get { 56 | return self.frame.origin.x 57 | } set(value) { 58 | self.frame = CGRect(x: value, y: top, width: width, height: height) 59 | } 60 | } 61 | 62 | var right: CGFloat { 63 | get { 64 | return left + width 65 | } set(value) { 66 | left = value - width 67 | } 68 | } 69 | 70 | var top: CGFloat { 71 | get { 72 | return self.frame.origin.y 73 | } set(value) { 74 | self.frame = CGRect(x: left, y: value, width: width, height: height) 75 | } 76 | } 77 | 78 | var bottom: CGFloat { 79 | get { 80 | return top + height 81 | } set(value) { 82 | top = value - height 83 | } 84 | } 85 | 86 | var width: CGFloat { 87 | get { 88 | return self.frame.size.width 89 | } set(value) { 90 | self.frame = CGRect(x: left, y: top, width: value, height: height) 91 | } 92 | } 93 | 94 | var height: CGFloat { 95 | get { 96 | return self.frame.size.height 97 | } set(value) { 98 | self.frame = CGRect(x: left, y: top, width: width, height: value) 99 | } 100 | } 101 | 102 | var origin: CGPoint { 103 | get { 104 | return self.frame.origin 105 | } set(value) { 106 | self.frame = CGRect(origin: value, size: self.frame.size) 107 | } 108 | } 109 | 110 | var size: CGSize { 111 | get { 112 | return self.frame.size 113 | } set(value) { 114 | self.frame = CGRect(origin: self.frame.origin, size: value) 115 | } 116 | } 117 | 118 | var centerX: CGFloat { 119 | get { 120 | return self.frame.origin.x + self.frame.size.width * 0.5 121 | } set(value) { 122 | var frame = self.frame 123 | frame.origin.x = value - frame.size.width * 0.5 124 | self.frame = frame 125 | } 126 | } 127 | 128 | var centerY: CGFloat { 129 | get { 130 | return self.frame.origin.y + self.frame.size.height * 0.5; 131 | } set(value) { 132 | var frame = self.frame 133 | frame.origin.y = value - frame.size.height * 0.5; 134 | self.frame = frame 135 | } 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /OverlayController/Extensions/CGGeometry+PKExtensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CGGeometry+PKExtensions.swift 3 | // PKExtensions 4 | // 5 | // Created by zhanghao on 2020/2/24. 6 | // Copyright © 2020 zhanghao. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | extension PKCGFloatExtensions { 12 | 13 | /// 将CGFloat转Int 14 | func toInt() -> Int { Int(base) } 15 | 16 | /// 将CGFloat转Double 17 | func toDouble() -> Double { Double(base) } 18 | 19 | /// 将CGFloat转String 20 | func toString() -> String { String(describing: base) } 21 | 22 | /// 返回CGFloat的中点 23 | func center() -> CGFloat { scaled(0.5) } 24 | 25 | /// 返回CGFloat的倍数 26 | func scaled(_ scale: CGFloat) -> CGFloat { base * scale } 27 | 28 | /// 将CGFloat转像素点 29 | func toPixel() -> CGFloat { scaled(UIScreen.main.scale) } 30 | 31 | /// 将像素点转换成CGFloat 32 | func fromPixel() -> CGFloat { base / UIScreen.main.scale } 33 | 34 | /// 将角度转弧度 35 | func degreesToRadians() -> CGFloat { (.pi * base) / 180.0 } 36 | 37 | /// 将弧度转角度 38 | func radiansToDegrees() -> CGFloat { (180.0 * base) / .pi } 39 | } 40 | 41 | extension PKCGPointExtensions { 42 | 43 | /// 返回两个点之间的距离 44 | static func distance(_ from: CGPoint, _ to: CGPoint) -> CGFloat { 45 | return sqrt(pow(to.x - from.x, 2) + pow(to.y - from.y, 2)) 46 | } 47 | 48 | /// 返回两个点之间的中点 49 | static func center(_ p1: CGPoint, _ p2: CGPoint) -> CGPoint { 50 | let px = fmax(p1.x, p2.x) + fmin(p1.x, p2.x) 51 | let py = fmax(p1.y, p2.y) + fmin(p1.y, p2.y) 52 | return CGPoint(x: px * 0.5, y: py * 0.5) 53 | } 54 | 55 | /// 判断当前点是否在圆形内 (center: 圆心 radius: 半径) 56 | func within(center: CGPoint, radius: CGFloat) -> Bool { 57 | let dx = fabs(Double(base.x - center.x)) 58 | let dy = fabs(Double(base.y - center.y)) 59 | return hypot(dx, dy) <= Double(radius) 60 | } 61 | 62 | /// 将CGPoint放大指定的倍数 63 | func scaled(_ scale: CGFloat) -> CGPoint { 64 | return CGPoint(x: base.x * scale, y: base.y * scale) 65 | } 66 | 67 | /// 将CGPoint向上取整 68 | func ceiled() -> CGPoint { 69 | return CGPoint(x: ceil(base.x), y: ceil(base.y)) 70 | } 71 | 72 | /// 将CGPoint向下取整 73 | func floored() -> CGPoint { 74 | return CGPoint(x: floor(base.x), y: floor(base.y)) 75 | } 76 | 77 | /// 将CGPoint四舍五入 78 | func rounded() -> CGPoint { 79 | return CGPoint(x: round(base.x), y: round(base.y)) 80 | } 81 | } 82 | 83 | extension PKCGSizeExtensions { 84 | 85 | /// 将CGSize放大指定的倍数 86 | func scaled(_ scale: CGFloat) -> CGSize { 87 | return CGSize(width: base.width * scale, height: base.height * scale) 88 | } 89 | 90 | /// 将CGSize向上取整 91 | func ceiled() -> CGSize { 92 | return CGSize(width: ceil(base.width), height: ceil(base.height)) 93 | } 94 | 95 | /// 将CGSize向下取整 96 | func floored() -> CGSize { 97 | return CGSize(width: floor(base.width), height: floor(base.height)) 98 | } 99 | 100 | /// 将CGSize四舍五入 101 | func rounded() -> CGSize { 102 | return CGSize(width: round(base.width), height: round(base.height)) 103 | } 104 | 105 | /// 判断CGSize是否有效(不包含零值) 106 | var isValid: Bool { 107 | return (base.width > 0 && base.height > 0) 108 | } 109 | } 110 | 111 | extension PKCGRectExtensions { 112 | 113 | /// 将CGRect放大指定的倍数 114 | func scaled(_ scale: CGFloat) -> CGRect { 115 | return CGRect(x: base.origin.x * scale, y: base.origin.y * scale, 116 | width: base.size.width * scale, height: base.size.height * scale) 117 | } 118 | 119 | /// 将CGRect向上取整 120 | func ceiled() -> CGRect { 121 | return CGRect(x: ceil(base.origin.x), y: ceil(base.origin.y), 122 | width: ceil(base.size.width), height: ceil(base.size.height)) 123 | } 124 | 125 | /// 将CGRect向下取整 126 | func floored() -> CGRect { 127 | return CGRect(x: floor(base.origin.x), y: floor(base.origin.y), 128 | width: floor(base.size.width), height: floor(base.size.height)) 129 | } 130 | 131 | /// 将CGRect四舍五入 132 | func rounded() -> CGRect { 133 | return CGRect(x: round(base.origin.x), y: round(base.origin.y), 134 | width: round(base.size.width), height: round(base.size.height)) 135 | } 136 | } 137 | 138 | public struct PKCGPointExtensions { 139 | fileprivate static var Base: CGPoint.Type { CGPoint.self } 140 | fileprivate var base: CGPoint 141 | fileprivate init(_ base: CGPoint) { self.base = base } 142 | } 143 | 144 | public extension CGPoint { 145 | var pk: PKCGPointExtensions { PKCGPointExtensions(self) } 146 | static var pk: PKCGPointExtensions.Type { PKCGPointExtensions.self } 147 | } 148 | 149 | public struct PKCGSizeExtensions { 150 | fileprivate static var Base: CGSize.Type { CGSize.self } 151 | fileprivate var base: CGSize 152 | fileprivate init(_ base: CGSize) { self.base = base } 153 | } 154 | 155 | public extension CGSize { 156 | var pk: PKCGSizeExtensions { PKCGSizeExtensions(self) } 157 | static var pk: PKCGSizeExtensions.Type { PKCGSizeExtensions.self } 158 | } 159 | 160 | public struct PKCGRectExtensions { 161 | fileprivate static var Base: CGRect.Type { CGRect.self } 162 | fileprivate var base: CGRect 163 | fileprivate init(_ base: CGRect) { self.base = base } 164 | } 165 | 166 | public extension CGRect { 167 | var pk: PKCGRectExtensions { PKCGRectExtensions(self) } 168 | static var pk: PKCGRectExtensions.Type { PKCGRectExtensions.self } 169 | } 170 | 171 | public struct PKCGFloatExtensions { 172 | fileprivate static var Base: CGFloat.Type { CGFloat.self } 173 | fileprivate var base: CGFloat 174 | fileprivate init(_ base: CGFloat) { self.base = base } 175 | } 176 | 177 | public extension CGFloat { 178 | var pk: PKCGFloatExtensions { PKCGFloatExtensions(self) } 179 | static var pk: PKCGFloatExtensions.Type { PKCGFloatExtensions.self } 180 | } 181 | -------------------------------------------------------------------------------- /OverlayController/Extensions/Double+PKExtensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Double+PKExtensions.swift 3 | // PKExtensions 4 | // 5 | // Created by zhanghao on 2020/2/24. 6 | // Copyright © 2020 zhanghao. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | public extension PKIntExtensions { 12 | 13 | /// 是否为偶数 14 | var isEven: Bool { return (base & 1 == 0) } 15 | 16 | /// 是否为奇数 17 | var isOdd: Bool { return (base & 1 != 0) } 18 | 19 | /// 转为Double 20 | var toDouble: Double { return Double(base) } 21 | 22 | /// 转为Float 23 | var toFloat: Float { return Float(base) } 24 | 25 | /// 转为CGFloat 26 | var toCGFloat: CGFloat { return CGFloat(base) } 27 | 28 | /// 转为String 29 | var toString: String { return String(base) } 30 | 31 | /// 转为UInt 32 | var toUInt: UInt { return UInt(base) } 33 | 34 | /// 转为Int32 35 | var toInt32: Int32 { return Int32(base) } 36 | } 37 | 38 | public extension PKDoubleExtensions { 39 | 40 | /// 将浮点数转Int 41 | func toInt() -> Int { Int(base) } 42 | 43 | /// 将浮点数转CGFloat 44 | func toCGFloat() -> CGFloat { CGFloat(base) } 45 | 46 | /// 将浮点数转String 47 | func toString() -> String { String(describing: base) } 48 | 49 | /// 将浮点数转NSNumber 50 | func toNumber() -> NSNumber { NSNumber(value: base) } 51 | 52 | /// 返回浮点数的二分之一 53 | func half() -> Double { multiplied(0.5) } 54 | 55 | /// 返回乘以scale后浮点数 56 | func multiplied(_ scale: Double) -> Double { return base * scale } 57 | 58 | /// 将浮点数四舍五入,自定义小数点后保留的位数 59 | /// 60 | /// 使用round()函数或NumberFormatter类,格式化的浮点数会出现误差, 61 | /// 因为这并非真正的四舍五入,而是遵循的"银行家算法",大致规则: 62 | /// `四舍六入五考虑,五后非零就进一,五后为零看奇偶,五前为偶应舍去,五前为奇要进一` 63 | /// 为此该函数内部使用了对浮点型计算更加精准的NSDecimalNumber类来避免精度误差。 64 | /// 但使用NSDecimalNumber并非没有此问题,不过保留两位小数时是精准的,如果保留多位结果还会按"银行家算法"计算,例如 65 | /// 66 | /// let w = 0.655665 67 | /// let m = 0.565655 68 | /// // 保留两位小数时:w = 0.66 m = 0.57 69 | /// // 保留五位小数时:w = 0.65567 m = 0.56565 (期望m应该为0.56566) 70 | /// 71 | /// 默认规则: 72 | /// 73 | /// let x: Double = 0.135 // x = 0.14 74 | /// let y: Double = 0.145 // y = 0.15 75 | /// 76 | /// - Parameters: 77 | /// - digits: 保留多少位小数,默认保留两位小数 78 | /// 79 | /// let a = 0.14562 80 | /// digits = 3 // a = 0.146 81 | /// digits = 4 // a = 0.1456 82 | /// 83 | /// - formatted: 是否格式化,默认YES 84 | /// 85 | /// let b = 0.15 86 | /// digits = 3 87 | /// formatted = true // b = 0.150 88 | /// formatted = false // b = 0.15 89 | /// 90 | /// - Returns: 指定保留位数的浮点数字符串(四舍五入) 91 | func stringValueRound(reserved digits: Int = 2, formatted: Bool = true) -> String { 92 | let number = NSDecimalNumber(value: base).pk_plainRound(digits) 93 | if formatted { return String(format: "%.*lf", digits, number.doubleValue) } 94 | return number.stringValue 95 | } 96 | 97 | /// 将浮点数四舍五入,自定义小数点后保留的位数 98 | /// 99 | /// 与`stringValueRound()`方法相同,在大量循环计算时性能相对更优,保留两位小数时可能存在误差 100 | func stringValue(reserved digits: Int = 2, formatted: Bool = true) -> String { 101 | let divisor = pow(10.0, Double(digits)) 102 | let numberValue = floor(base * divisor + 0.5) / divisor 103 | if formatted { return String(format: "%.*lf", digits, numberValue) } 104 | return String(describing: numberValue) 105 | } 106 | 107 | /// 直接取浮点数不四舍五入,自定义小数点后保留的位数 108 | /// 109 | /// 默认规则: 110 | /// 111 | /// let x: Double = 0.135 // x = 0.13 112 | /// let y: Double = 0.145 // y = 0.14 113 | /// 114 | /// - Parameters: 115 | /// - digits: 保留多少位小数,默认保留两位小数 116 | /// 117 | /// let a = 0.14562 118 | /// digits = 3 // a = 0.145 119 | /// digits = 4 // a = 0.1456 120 | /// 121 | /// - formatted: 是否格式化,默认YES 122 | /// 123 | /// let b = 0.15 124 | /// digits = 3 125 | /// formatted = true // b = 0.150 126 | /// formatted = false // b = 0.15 127 | /// 128 | /// - Returns: 指定保留位数的浮点数字符串(不四舍五入) 129 | func stringValueUnestimated(reserved digits: Int = 2, formatted: Bool = true) -> String { 130 | let divisor = pow(10.0, Double(digits)) 131 | let numberValue = Double(Int(base * divisor)) / divisor 132 | if formatted { return String(format: "%.*lf", digits, numberValue) } 133 | return String(describing: self) 134 | } 135 | 136 | /// 将浮点数百分比化,自定义保留位数 137 | /// 138 | /// 默认规则: 139 | /// 140 | /// let x: Double = 0.10125 // x = 10.14% 141 | /// let y: Double = 0.10155 // y = 10.15% 142 | /// 143 | /// - Parameters: 144 | /// - digits: 保留多少位小数,默认保留两位小数 145 | /// 146 | /// let a = 0.145625 147 | /// digits = 3 // a = 14.563% 148 | /// digits = 4 // a = 14.5625% 149 | /// 150 | /// - formatted: 是否格式化,默认YES 151 | /// 152 | /// let b = 0.152 153 | /// digits = 3 154 | /// formatted = true // b = 15.200% 155 | /// formatted = false // b = 15.2% 156 | /// 157 | /// - Returns: 转为百分比形式的字符串(四舍五入) 158 | func stringValuePercent(reserved digits: Int = 2, formatted: Bool = true) -> String { 159 | let nup = NSDecimalNumber(value: base).pk_plainRound(digits + 2) 160 | let newp = nup.multiplying(byPowerOf10: 2) 161 | if formatted { return String(format: "%.*lf%%", digits, newp.doubleValue) } 162 | return newp.stringValue + "%" 163 | } 164 | 165 | /// 获取万亿字符串 166 | /// 167 | /// let x: Double = 125601 // x = 12.56万 168 | /// let y: Double = 1256010101 // y = 12.56亿 169 | /// let z: Double = 12560101015001 // z = 12.56万亿 170 | /// 171 | /// - Parameters: 172 | /// - digits: 保留多少位小数,默认保留两位小数 173 | /// - formatted: 是否格式化,默认YES 174 | /// 175 | /// let a = 12000 176 | /// digits = 3 177 | /// formatted = true // a = 1.200万 178 | /// formatted = false // a = 1.2万 179 | /// 180 | /// - Returns: 万亿/亿/万为后缀的字符串 181 | func stringValueShortened(reserved digits: Int = 2, formatted: Bool = true) -> String { 182 | func make(_ value: Double) -> (index: Int, unit: String) { 183 | if value < 10000 { return (0, "") 184 | } else if value < 100000000 { return (4, "万") 185 | } else if value < 1000000000000 { return (8, "亿") 186 | } else { return (12, "万亿") } 187 | } 188 | let tuples = make(fabs(base)) 189 | let numberValue = base / pow(10.0, Double(tuples.index)) 190 | return numberValue.pk.stringValueRound(reserved: digits, formatted: formatted) + tuples.unit 191 | } 192 | 193 | /// 获取浮点数小数部分(四舍五入) 194 | /// 195 | /// let x: Double = 10.135 // x = 0.14 196 | /// let y: Double = 10.145 // y = 0.15 197 | /// 198 | /// - Parameters: 199 | /// - digits: 保留多少位小数,默认保留两位小数 200 | /// 201 | /// let a = 10.13505 202 | /// digits = 4 // a = 0.1351 203 | /// digits = 5 // a = 0.13505 204 | /// 205 | /// - formatted: 是否格式化,默认YES 206 | /// 207 | /// let b = 10.13 208 | /// digits = 4 209 | /// formatted = true // a = 0.1300 210 | /// formatted = false // a = 0.13 211 | /// 212 | /// - Returns: 只有小数部分的字符串(四舍五入) 213 | func stringValueDecimals(reserved digits: Int = 2, formatted: Bool = true) -> String { 214 | let value = base - Double(Int(base)) 215 | let number = NSDecimalNumber(value: value).pk_plainRound(digits) 216 | if formatted { return String(format: "%.*lf", digits, number.doubleValue) } 217 | return number.stringValue 218 | } 219 | 220 | /// 将浮点数转成货币书写形式 221 | /// 222 | /// let x: Double = 1209001 223 | /// // digits set 2, x = 1,209,001.00 224 | /// // formatted set NO, x = 1,209,001 225 | /// // signed set YES, x = $1,209,001 226 | /// 227 | /// - Parameters: 228 | /// - digits: 保留多少位小数,默认保留两位小数 229 | /// - formatted: 是否格式化,默认YES 230 | /// - signed: 是否显示货币符号,默认NO 231 | /// 232 | /// - Returns: 返回货币书写形式 (即每三位使用逗号分隔) 233 | func stringValueCurrency(reserved digits: Int = 2, formatted: Bool = true, signed: Bool = false) -> String { 234 | let formatter = NumberFormatter() 235 | formatter.numberStyle = .currency 236 | formatter.maximumFractionDigits = digits 237 | formatter.minimumFractionDigits = formatted ? digits : 0 238 | if signed { return formatter.string(from: NSNumber(value: base))! } 239 | formatter.locale = Locale(identifier: "zh_CN") 240 | var result = formatter.string(from: NSNumber(value: base))! 241 | result.remove(at: result.startIndex) 242 | return result 243 | } 244 | 245 | /// 将浮点数转成人民币大写朗读形式 246 | /// 247 | /// let x: Double = 12092 // x = 壹万贰仟零玖拾贰圆整 248 | /// let y: Double = 12092.5212 // y = 壹万贰仟零玖拾贰圆伍角贰分 249 | /// let z: Double = 2152092.056 // z = 贰佰壹拾伍万贰仟零玖拾贰圆零角伍分 250 | /// 251 | /// - Returns: 返回人民币大写朗读形式,保留到角分 252 | func stringRmbCapitalized() -> String { 253 | let formatter = NumberFormatter() 254 | formatter.locale = Locale(identifier: "zh_CN") 255 | formatter.numberStyle = .spellOut 256 | formatter.maximumFractionDigits = 2 257 | guard let value = formatter.string(from: NSNumber(value: base)) else { return "" } 258 | 259 | func mapC(_ c: Character) -> String { 260 | switch c { 261 | case "千": return "仟" 262 | case "百": return "佰" 263 | case "十": return "拾" 264 | case "九": return "玖" 265 | case "八": return "捌" 266 | case "七": return "柒" 267 | case "六": return "陆" 268 | case "五": return "伍" 269 | case "四": return "肆" 270 | case "三": return "叁" 271 | case "二": return "贰" 272 | case "一": return "壹" 273 | case "〇": return "零" 274 | case "点": return "圆" 275 | default: return String(c) 276 | } 277 | } 278 | 279 | if let range = value.range(of: "点") { 280 | let lower = value[...range.lowerBound].map({ mapC($0) }).joined() 281 | let upper = value[range.upperBound.. 1 else { return part } 284 | let char = upper[upper.index(after: upper.startIndex)] 285 | return part + mapC(char) + "分" 286 | } else { 287 | return value.map({ mapC($0) }).joined() + "圆整" 288 | } 289 | } 290 | } 291 | 292 | private extension NSDecimalNumber { 293 | func pk_plainRound(_ places: Int) -> NSDecimalNumber { 294 | let handler = NSDecimalNumberHandler(roundingMode: .plain, scale: Int16(places), raiseOnExactness: false, raiseOnOverflow: false, raiseOnUnderflow: false, raiseOnDivideByZero: false) 295 | return self.rounding(accordingToBehavior: handler) 296 | } 297 | } 298 | 299 | public struct PKIntExtensions { 300 | fileprivate static var Base: Int.Type { Int.self } 301 | fileprivate var base: Int 302 | fileprivate init(_ base: Int) { self.base = base } 303 | } 304 | 305 | public extension Int { 306 | var pk: PKIntExtensions { PKIntExtensions(self) } 307 | static var pk: PKIntExtensions.Type { PKIntExtensions.self } 308 | } 309 | 310 | public struct PKDoubleExtensions { 311 | fileprivate static var Base: Double.Type { Double.self } 312 | fileprivate var base: Double 313 | fileprivate init(_ base: Double) { self.base = base } 314 | } 315 | 316 | public extension Double { 317 | var pk: PKDoubleExtensions { PKDoubleExtensions(self) } 318 | static var pk: PKDoubleExtensions.Type { PKDoubleExtensions.self } 319 | } 320 | -------------------------------------------------------------------------------- /OverlayController/Extensions/String+PKExtensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // String+PKExtensions.swift 3 | // PKExtensions 4 | // 5 | // Created by zhanghao on 2020/2/24. 6 | // Copyright © 2020 zhanghao. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | public extension PKStringExtensions { 12 | 13 | /// 检查字符串是否为空或只包含空白和换行字符 14 | var isBlank: Bool { 15 | let trimmed = base.trimmingCharacters(in: .whitespacesAndNewlines) 16 | return trimmed.isEmpty 17 | } 18 | 19 | /// 返回字符串中出现指定字符的第一个索引 20 | func index(of char: Character) -> Int? { 21 | for (index, c) in base.enumerated() where c == char { 22 | return index 23 | } 24 | return nil 25 | } 26 | 27 | /// 字符串查找子串返回NSRange 28 | func range(of subString: String?) -> NSRange { 29 | guard let subValue = subString else { return NSRange(location: 0, length: 0) } 30 | let swRange = base.range(of: subValue) 31 | return NSRange(swRange!, in: base) 32 | } 33 | 34 | /// 计算文本所对应的视图大小 35 | func size(constraint size: CGSize, font: UIFont = UIFont.systemFont(ofSize: UIFont.systemFontSize), lineBreakMode: NSLineBreakMode? = .byCharWrapping) -> CGSize { 36 | var attrib: [NSAttributedString.Key: Any] = [NSAttributedString.Key.font: font] 37 | if lineBreakMode != nil { 38 | let paragraphStyle = NSMutableParagraphStyle() 39 | paragraphStyle.lineBreakMode = lineBreakMode! 40 | attrib.updateValue(paragraphStyle, forKey: NSAttributedString.Key.paragraphStyle) 41 | } 42 | let rect = (base as NSString).boundingRect(with: size, options: .usesLineFragmentOrigin, attributes: attrib, context: nil) 43 | return CGSize(width: ceil(rect.width), height: ceil(rect.height)) 44 | } 45 | 46 | /// 计算文本宽度 (约束高度) 47 | func width(constraint height: CGFloat, font: UIFont, lineBreakMode: NSLineBreakMode? = .byCharWrapping) -> CGFloat { 48 | let size = CGSize(width: CGFloat(Double.greatestFiniteMagnitude), height: height) 49 | return self.size(constraint: size, font: font, lineBreakMode: lineBreakMode).width 50 | } 51 | 52 | /// 计算文本高度 (约束宽度) 53 | func height(constraint width: CGFloat, font: UIFont, lineBreakMode: NSLineBreakMode? = .byCharWrapping) -> CGFloat { 54 | let size = CGSize(width: width, height: CGFloat(Double.greatestFiniteMagnitude)) 55 | return self.size(constraint: size, font: font, lineBreakMode: lineBreakMode).height 56 | } 57 | } 58 | 59 | public extension PKStringExtensions { 60 | 61 | /// 将数字金额字符串转成人民币朗读形式 62 | func rmbCapitalized() -> String { 63 | guard let number = Double(base) else { return "" } 64 | return number.pk.stringRmbCapitalized() 65 | } 66 | 67 | /// 检查字符串中是否包含Emoji 68 | func containsEmoji() -> Bool { 69 | for i in 0.. Void) { 15 | DispatchQueue.main.asyncAfter(deadline: .now() + seconds, execute: work) 16 | } 17 | 18 | /// 测试闭包内运行耗时(单位:毫秒) 19 | static func runThisElapsed(_ handler: @escaping () -> Void) -> Double { 20 | let a = CFAbsoluteTimeGetCurrent() 21 | handler() 22 | let b = CFAbsoluteTimeGetCurrent() 23 | return (b - a) * 1000.0; // to millisecond 24 | } 25 | 26 | /// 重复运行闭包任务 seconds:间隔时间,取消运行:timer.invalidate() 27 | static func runThisEvery(seconds: TimeInterval, handler: @escaping (Timer?) -> Void) -> Timer { 28 | let fireDate = CFAbsoluteTimeGetCurrent() 29 | let timer = CFRunLoopTimerCreateWithHandler(kCFAllocatorDefault, fireDate, seconds, 0, 0, handler) 30 | CFRunLoopAddTimer(CFRunLoopGetCurrent(), timer, CFRunLoopMode.commonModes) 31 | return timer! 32 | } 33 | } 34 | 35 | public struct PKTimerExtensions { 36 | fileprivate static var Base: Timer.Type { Timer.self } 37 | fileprivate var base: Timer 38 | fileprivate init(_ base: Timer) { self.base = base } 39 | } 40 | 41 | public extension Timer { 42 | var pk: PKTimerExtensions { PKTimerExtensions(self) } 43 | static var pk: PKTimerExtensions.Type { PKTimerExtensions.self } 44 | } 45 | -------------------------------------------------------------------------------- /OverlayController/Extensions/UIApplication+PKExtensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIApplication+PKExtensions.swift 3 | // PKExtensions 4 | // 5 | // Created by zhanghao on 2020/2/23. 6 | // Copyright © 2020 zhanghao. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | public extension PKApplicationExtensions { 12 | 13 | /// 获取应用程序的主窗口 14 | static var keyWindow: UIWindow? { 15 | if let window = UIApplication.shared.delegate?.window as? UIWindow { 16 | return window; 17 | } 18 | 19 | if #available(iOS 13.0, *) { 20 | return UIApplication.shared.windows.first 21 | } else { 22 | return UIApplication.shared.keyWindow 23 | } 24 | } 25 | 26 | /// 获取当前程序的顶层控制器 27 | static func topViewController() -> UIViewController? { 28 | func findTopViewController(_ current: UIViewController?) -> UIViewController? { 29 | if let presented = current?.presentedViewController { 30 | return findTopViewController(presented) 31 | } 32 | 33 | if let tabbarController = current as? UITabBarController { 34 | return findTopViewController(tabbarController.selectedViewController) 35 | } 36 | 37 | if let navigationController = current as? UINavigationController { 38 | return findTopViewController(navigationController.topViewController) 39 | } 40 | return current 41 | } 42 | return findTopViewController(keyWindow?.rootViewController) 43 | } 44 | 45 | /// 程序后台挂起时运行该闭包任务 46 | func runInBackground(_ closure: @escaping () -> Void, expirationHandler: (() -> Void)? = nil) { 47 | DispatchQueue.main.async { 48 | let taskID: UIBackgroundTaskIdentifier 49 | if let expirationHandler = expirationHandler { 50 | taskID = self.base.beginBackgroundTask(expirationHandler: expirationHandler) 51 | } else { 52 | taskID = self.base.beginBackgroundTask(expirationHandler: { }) 53 | } 54 | closure() 55 | self.base.endBackgroundTask(taskID) 56 | } 57 | } 58 | } 59 | 60 | public struct PKApplicationExtensions { 61 | fileprivate static var Base: UIApplication.Type { UIApplication.self } 62 | fileprivate var base: UIApplication 63 | fileprivate init(_ base: UIApplication) { self.base = base } 64 | } 65 | 66 | public extension UIApplication { 67 | var pk: PKApplicationExtensions { PKApplicationExtensions(self) } 68 | static var pk: PKApplicationExtensions.Type { PKApplicationExtensions.self } 69 | } 70 | -------------------------------------------------------------------------------- /OverlayController/Extensions/UIBarButtonItem+PKExtensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIBarButtonItem+PKExtensions.swift 3 | // PKExtensions 4 | // 5 | // Created by zhanghao on 2020/2/24. 6 | // Copyright © 2020 zhanghao. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | public extension PKBarButtonItemExtensions where Base: UIBarButtonItem { 12 | 13 | /// 使用系统样式初始化UIBarButtonItem并添加闭包事件 14 | static func make(systemItem: UIBarButtonItem.SystemItem, handler: @escaping (_ sender: UIBarButtonItem) -> Void) -> Base { 15 | let buttonItem = Base.init(barButtonSystemItem: systemItem, target: nil, action: nil) 16 | buttonItem.pk_addAction(handler: handler) 17 | return buttonItem 18 | } 19 | 20 | /// 初始化UIBarButtonItem设置标题并添加闭包事件 21 | static func make(title: String?, style: UIBarButtonItem.Style = .plain, handler: @escaping (_ sender: UIBarButtonItem) -> Void) -> Base { 22 | let buttonItem = Base(title: title, style: style, target: nil, action: nil) 23 | buttonItem.pk_addAction(handler: handler) 24 | return buttonItem 25 | } 26 | 27 | /// 初始化UIBarButtonItem设置图片并添加闭包事件 28 | static func make(image: UIImage?, style: UIBarButtonItem.Style = .plain, handler: @escaping (_ sender: UIBarButtonItem) -> Void) -> Base { 29 | let buttonItem = Base(image: image, style: style, target: nil, action: nil) 30 | buttonItem.pk_addAction(handler: handler) 31 | return buttonItem 32 | } 33 | 34 | /// 为当前UIBarButtonItem添加闭包事件 35 | func addAction(handler: @escaping (_ sender: UIBarButtonItem) -> Void) { 36 | base.pk_addAction(handler: handler) 37 | } 38 | } 39 | 40 | private var UIBarButtonItemAssociatedWrappersKey: Void? 41 | 42 | private extension UIBarButtonItem { 43 | var pk_wrapper: _PKBarButtonItemWrapper? { 44 | get { 45 | return objc_getAssociatedObject(self, &UIBarButtonItemAssociatedWrappersKey) as? _PKBarButtonItemWrapper 46 | } set { 47 | objc_setAssociatedObject(self, &UIBarButtonItemAssociatedWrappersKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) 48 | } 49 | } 50 | 51 | func pk_addAction(handler: @escaping (_ sender: UIBarButtonItem) -> Void) { 52 | let target = _PKBarButtonItemWrapper(handler: handler) 53 | pk_wrapper = target 54 | self.target = target 55 | self.action = target.method 56 | } 57 | } 58 | 59 | private class _PKBarButtonItemWrapper { 60 | var block: ((_ sender: UIBarButtonItem) -> Void)? 61 | let method = #selector(invoke(_:)) 62 | 63 | init(handler: @escaping (_ sender: UIBarButtonItem) -> Void) { 64 | block = handler 65 | } 66 | 67 | @objc func invoke(_ sender: UIBarButtonItem) { 68 | block?(sender) 69 | } 70 | } 71 | 72 | public struct PKBarButtonItemExtensions { 73 | fileprivate var base: Base 74 | fileprivate init(_ base: Base) { self.base = base } 75 | } 76 | 77 | public protocol PKBarButtonItemExtensionsCompatible {} 78 | 79 | public extension PKBarButtonItemExtensionsCompatible { 80 | static var pk: PKBarButtonItemExtensions.Type { PKBarButtonItemExtensions.self } 81 | var pk: PKBarButtonItemExtensions { get{ PKBarButtonItemExtensions(self) } set{} } 82 | } 83 | 84 | extension UIBarButtonItem: PKBarButtonItemExtensionsCompatible {} 85 | -------------------------------------------------------------------------------- /OverlayController/Extensions/UIButton+PKExtensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIButton+PKExtensions.swift 3 | // PKExtensions 4 | // 5 | // Created by zhanghao on 2020/2/24. 6 | // Copyright © 2020 zhanghao. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | public extension PKViewExtensions where Base: UIButton { 12 | 13 | /// 设置背景色 14 | func setBackgroundColor(_ color: UIColor, forState: UIControl.State) { 15 | UIGraphicsBeginImageContext(CGSize(width: 1, height: 1)) 16 | UIGraphicsGetCurrentContext()?.setFillColor(color.cgColor) 17 | UIGraphicsGetCurrentContext()?.fill(CGRect(x: 0, y: 0, width: 1, height: 1)) 18 | let colorImage = UIGraphicsGetImageFromCurrentImageContext() 19 | UIGraphicsEndImageContext() 20 | base.setBackgroundImage(colorImage, for: forState) 21 | } 22 | } 23 | 24 | public extension PKViewExtensions where Base: UIButton { 25 | 26 | /// 判断活动指示器是否正在显示中 27 | var isShowingIndicator: Bool { 28 | objc_getAssociatedObject(base, &UIButtonAssociatedIndicatorShowingKey) as? Bool ?? false 29 | } 30 | 31 | /// 显示活动指示器 (显示时将置空标题图片,指示器消失则恢复) 32 | /// 33 | /// - Parameters: 34 | /// - style: 指示器样式 35 | /// - color: 颜色 36 | /// - cleared: 是否清除背景色,默认false 37 | func showIndicator(style: UIActivityIndicatorView.Style, color: UIColor = .white, cleared: Bool = false) { 38 | guard !isShowingIndicator else { return } 39 | if !base.translatesAutoresizingMaskIntoConstraints { 40 | base.superview?.layoutIfNeeded() 41 | } 42 | 43 | base.isUserInteractionEnabled = false 44 | 45 | func setAssociated(_ key: UnsafeRawPointer, _ value: Any?) { 46 | objc_setAssociatedObject(base, key, value, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) 47 | } 48 | 49 | if let bgColor = base.backgroundColor, cleared { 50 | setAssociated(&UIButtonAssociatedIndicatorBgColorKey, bgColor) 51 | base.backgroundColor = .clear 52 | } 53 | 54 | if let title = base.title(for: .normal) { 55 | setAssociated(&UIButtonAssociatedNormalTitleKey, title) 56 | base.setTitle("", for: .normal) 57 | } 58 | 59 | if let image = base.image(for: .normal) { 60 | setAssociated(&UIButtonAssociatedNormalImageKey, image) 61 | base.setImage(nil, for: .normal) 62 | } 63 | 64 | let indicator = UIActivityIndicatorView(style: style) 65 | indicator.color = color 66 | indicator.transform = CGAffineTransform(scaleX: 1.0, y: 1.0) 67 | indicator.center = CGPoint(x: base.bounds.width / 2, y: base.bounds.height / 2) 68 | indicator.startAnimating() 69 | base.addSubview(indicator) 70 | setAssociated(&UIButtonAssociatedIndicatorShowingKey, true) 71 | setAssociated(&UIButtonAssociatedIndicatorViewKey, indicator) 72 | } 73 | 74 | /// 显示活动指示器并自定义文本 75 | func showIndicatorText(_ text: String? = nil, style: UIActivityIndicatorView.Style, color: UIColor = .white) { 76 | guard !isShowingIndicator else { return } 77 | if !base.translatesAutoresizingMaskIntoConstraints { 78 | base.superview?.layoutIfNeeded() 79 | } 80 | 81 | base.isUserInteractionEnabled = false 82 | 83 | func setAssociated(_ key: UnsafeRawPointer, _ value: Any?) { 84 | objc_setAssociatedObject(base, key, value, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) 85 | } 86 | 87 | let indicator = UIActivityIndicatorView.init(style: style) 88 | indicator.color = color 89 | indicator.startAnimating() 90 | base.addSubview(indicator) 91 | setAssociated(&UIButtonAssociatedIndicatorShowingKey, true) 92 | setAssociated(&UIButtonAssociatedIndicatorViewKey, indicator) 93 | 94 | let title = base.title(for: .normal) ?? "" 95 | setAssociated(&UIButtonAssociatedNormalTitleKey, title) 96 | setAssociated(&UIButtonAssociatedTitleEdgeInsetsKey, base.titleEdgeInsets) 97 | base.setTitle(text, for: .normal) 98 | let size = base.titleLabel!.sizeThatFits(base.bounds.size) 99 | let padding = (base.bounds.size.width - size.width) / 2, spacing: CGFloat = 15.0; 100 | let offset = (base.bounds.width - indicator.bounds.width - size.width - spacing) / 2 101 | indicator.center = CGPoint(x: offset + indicator.bounds.width / 2, y: base.bounds.height / 2) 102 | base.titleEdgeInsets = UIEdgeInsets(top: 0, left: padding, bottom: 0, right: -padding + offset * 2) 103 | 104 | if let image = base.image(for: .normal) { 105 | setAssociated(&UIButtonAssociatedNormalImageKey, image) 106 | base.setImage(nil, for: .normal) 107 | } 108 | } 109 | 110 | /// 隐藏指示器 111 | func hideIndicator() { 112 | guard isShowingIndicator else { return } 113 | 114 | if let indicator = objc_getAssociatedObject(base, &UIButtonAssociatedIndicatorViewKey) as? UIActivityIndicatorView { 115 | indicator.stopAnimating() 116 | indicator.removeFromSuperview() 117 | } 118 | 119 | if let bgColor = objc_getAssociatedObject(base, &UIButtonAssociatedIndicatorBgColorKey) { 120 | base.backgroundColor = bgColor as? UIColor 121 | } 122 | 123 | if let title = objc_getAssociatedObject(base, &UIButtonAssociatedNormalTitleKey) { 124 | base.setTitle(title as? String, for: .normal) 125 | } 126 | 127 | if let image = objc_getAssociatedObject(base, &UIButtonAssociatedNormalImageKey) { 128 | base.setImage(image as? UIImage, for: .normal) 129 | } 130 | 131 | if let titleInsets = objc_getAssociatedObject(base, &UIButtonAssociatedTitleEdgeInsetsKey) { 132 | base.titleEdgeInsets = titleInsets as? UIEdgeInsets ?? UIEdgeInsets.zero 133 | } 134 | 135 | if let imageInsets = objc_getAssociatedObject(base, &UIButtonAssociatedImageEdgeInsetsKey) { 136 | base.imageEdgeInsets = imageInsets as? UIEdgeInsets ?? UIEdgeInsets.zero 137 | } 138 | 139 | base.isUserInteractionEnabled = true 140 | objc_setAssociatedObject(base, &UIButtonAssociatedIndicatorShowingKey, false, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) 141 | 142 | func nilAssociated(_ key: UnsafeRawPointer) { 143 | objc_setAssociatedObject(base, key, nil, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) 144 | } 145 | nilAssociated(&UIButtonAssociatedIndicatorBgColorKey) 146 | nilAssociated(&UIButtonAssociatedNormalTitleKey) 147 | nilAssociated(&UIButtonAssociatedNormalImageKey) 148 | nilAssociated(&UIButtonAssociatedTitleEdgeInsetsKey) 149 | nilAssociated(&UIButtonAssociatedImageEdgeInsetsKey) 150 | } 151 | } 152 | 153 | private var UIButtonAssociatedIndicatorShowingKey: Void? 154 | private var UIButtonAssociatedIndicatorViewKey: Void? 155 | private var UIButtonAssociatedIndicatorBgColorKey: Void? 156 | private var UIButtonAssociatedNormalTitleKey: Void? 157 | private var UIButtonAssociatedNormalImageKey: Void? 158 | private var UIButtonAssociatedTitleEdgeInsetsKey: Void? 159 | private var UIButtonAssociatedImageEdgeInsetsKey: Void? 160 | -------------------------------------------------------------------------------- /OverlayController/Extensions/UIColor+PKExtensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIColor+PKExtensions.swift 3 | // PKExtensions 4 | // 5 | // Created by zhanghao on 2020/2/24. 6 | // Copyright © 2020 zhanghao. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | public extension PKColorExtensions { 12 | 13 | /// 返回相同RGB值对应的颜色 14 | static func rgb(same value: CGFloat) -> UIColor { 15 | return UIColor(red: value / 255, green: value / 255, blue: value / 255, alpha: 1) 16 | } 17 | 18 | /// 返回RGBA值对应的颜色 19 | static func rgba(red: CGFloat, green: CGFloat, blue: CGFloat, alpha: CGFloat = 1) -> UIColor { 20 | return UIColor(red: red / 255, green: green / 255, blue: blue / 255, alpha: alpha) 21 | } 22 | 23 | /// 返回十六进制颜色值对应的颜色 24 | static func hex(_ hexValue: Int) -> UIColor { 25 | let red = CGFloat(CGFloat((hexValue & 0xFF0000) >> 16)/255.0) 26 | let green = CGFloat(CGFloat((hexValue & 0x00FF00) >> 8)/255.0) 27 | let blue = CGFloat(CGFloat((hexValue & 0x0000FF) >> 0)/255.0) 28 | return UIColor(red: red, green: green, blue: blue, alpha: 1.0) 29 | } 30 | 31 | /// 返回十六进制颜色字符串对应的RGBA颜色 32 | static func hex(_ hexString: String, alpha: CGFloat = 1.0) -> UIColor? { 33 | var format = hexString.replacingOccurrences(of: "0x", with: "") 34 | format = format.replacingOccurrences(of: "#", with: "") 35 | guard let hexValue = Int(format, radix: 16) else { return nil } 36 | return hex(hexValue) 37 | } 38 | 39 | /// 返回随机颜色 40 | static func random(randomAlpha: Bool = false) -> UIColor { 41 | let randomRed = CGFloat(arc4random() % 255) 42 | let randomGreen = CGFloat(arc4random() % 255) 43 | let randomBlue = CGFloat(arc4random() % 255) 44 | let alpha = randomAlpha ? CGFloat.random(in: 0.0...1.0) : 1.0 45 | return rgba(red: randomRed, green: randomGreen, blue: randomBlue, alpha: alpha) 46 | } 47 | 48 | /// 返回颜色的Red值 49 | var redComponent: Int { 50 | var r: CGFloat = 0 51 | base.getRed(&r, green: nil, blue: nil, alpha: nil) 52 | return Int(r * 255) 53 | } 54 | 55 | /// 返回颜色的Green值 56 | var greenComponent: Int { 57 | var g: CGFloat = 0 58 | base.getRed(nil, green: &g, blue: nil, alpha: nil) 59 | return Int(g * 255) 60 | } 61 | 62 | /// 返回颜色的Blue值 63 | var blueComponent: Int { 64 | var b: CGFloat = 0 65 | base.getRed(nil, green: nil, blue: &b, alpha: nil) 66 | return Int(b * 255) 67 | } 68 | 69 | /// 返回颜色的Alpha值 70 | var alpha: CGFloat { 71 | var a: CGFloat = 0 72 | base.getRed(nil, green: nil, blue: nil, alpha: &a) 73 | return a 74 | } 75 | } 76 | 77 | public extension PKColorExtensions { 78 | 79 | /// Excellent color schemes 80 | enum ColorSchemes { 81 | case granite 82 | case tangerine 83 | case fairy 84 | case cooler 85 | case slate 86 | case docks 87 | case haze 88 | case sunset 89 | case moss 90 | case greenDominates 91 | case gentle 92 | } 93 | 94 | /// 获取一套配色方案 95 | static func schemes(_ scheme: ColorSchemes = .sunset) -> [Int] { 96 | switch scheme { 97 | case .granite: 98 | return [0x3B7B9A, 0x70AFCE, 0xA5DEF1, 0x89d4db] 99 | case .tangerine: 100 | return [0xFF6D00, 0xFF9201, 0xFFAB40, 0xFFD180] 101 | case .fairy: 102 | return [0xA163F7, 0x6F88FC, 0x45E3FF, 0xFF7582] 103 | case .cooler: 104 | return [0x4BB6F4, 0x1F9CE4, 0x3E60C1, 0x5983FC] 105 | case .slate: 106 | return [0x5B7FA7, 0x508DA3, 0xA5DEF1, 0xFFFFFF] 107 | case .docks: 108 | return [0x43506C, 0xEF4B4C, 0x3D619B, 0xE9E9EB] 109 | case .haze: 110 | return [0x0F6BAE, 0x248BD6, 0x83BBFF, 0xC6CDFF] 111 | case .sunset: 112 | return [0x355C7D, 0x725A7A, 0xC56C86, 0xFF7582] 113 | case .moss: 114 | return [0x006270, 0x009394, 0x00E0C7, 0xF0FFF0] 115 | case .greenDominates: 116 | return [0x00738C, 0x81B0B2, 0xD6EAD4, 0x5BA8A0, 0x97A675, 117 | 0x96CA00, 0xC5DF56, 0x3B5284, 0xB3C8CD, 0x8AB186, 118 | 0xB3DA61, 0xCDE460, 0x6ECD8E, 0x539D73, 0x2B7337, 119 | 0x1C5A41, 0x2A8F68, 0x5AAF76, 0x498428, 0x336A29] 120 | case .gentle: 121 | return [0x478BA2, 0xDE5B6D, 0xE97658, 0xF2A490, 0xB9D4DB, 0x89d4db] 122 | } 123 | } 124 | 125 | /// 获取某套配色方法的随机色 126 | static func random(_ scheme: ColorSchemes) -> UIColor { 127 | let values = schemes(scheme) 128 | return UIColor.pk.hex(values.randomElement()!) 129 | } 130 | } 131 | 132 | public struct PKColorExtensions { 133 | fileprivate static var Base: UIColor.Type { UIColor.self } 134 | fileprivate var base: UIColor 135 | fileprivate init(_ base: UIColor) { self.base = base } 136 | } 137 | 138 | public extension UIColor { 139 | var pk: PKColorExtensions { PKColorExtensions(self) } 140 | static var pk: PKColorExtensions.Type { PKColorExtensions.self } 141 | } 142 | -------------------------------------------------------------------------------- /OverlayController/Extensions/UIControl+PKExtensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIControl+PKExtensions.swift 3 | // PKExtensions 4 | // 5 | // Created by zhanghao on 2020/2/24. 6 | // Copyright © 2020 zhanghao. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | public extension PKViewExtensions where Base: UIControl { 12 | 13 | /// 为UIControl扩大响应区域 14 | func enlargeTouch(insets: UIEdgeInsets) { 15 | base.pk_enlargeTouchInsets = insets 16 | } 17 | 18 | /// 为UIControl添加闭包点击事件 19 | func addAction(for controlEvents: UIControl.Event, handler: @escaping (_ sender: UIControl) -> Void) { 20 | base.pk_addAction(for: controlEvents, handler: handler) 21 | } 22 | 23 | /// 是否存在对应的闭包事件 24 | func hasAction(for controlEvents: UIControl.Event) -> Bool { 25 | base.pk_hasAction(for: controlEvents) 26 | } 27 | 28 | /// 移除对应的闭包事件 29 | func removeAction(for controlEvents: UIControl.Event) { 30 | base.pk_removeAction(for: controlEvents) 31 | } 32 | } 33 | 34 | private var UIControlAssociatedEnlargeTouchAreaKey: Void? 35 | 36 | extension UIControl { 37 | fileprivate var pk_enlargeTouchInsets: UIEdgeInsets { 38 | get { 39 | return objc_getAssociatedObject(self, &UIControlAssociatedEnlargeTouchAreaKey) as? UIEdgeInsets ?? UIEdgeInsets.zero 40 | } set { 41 | objc_setAssociatedObject(self, &UIControlAssociatedEnlargeTouchAreaKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) 42 | } 43 | } 44 | 45 | open override func point(inside point: CGPoint, with event: UIEvent?) -> Bool { 46 | let insets = pk_enlargeTouchInsets 47 | let touchRect = CGRect(x: bounds.origin.x - insets.left, 48 | y: bounds.origin.y - insets.left, 49 | width: bounds.size.width + insets.left + insets.right, 50 | height: bounds.size.height + insets.top + insets.bottom) 51 | return touchRect.contains(point) 52 | } 53 | } 54 | 55 | private var UIControlAssociatedWrappersKey: Void? 56 | 57 | private extension UIControl { 58 | var pk_wrappers: [UInt:_PKControlWrapper]? { 59 | get { 60 | objc_getAssociatedObject(self, &UIControlAssociatedWrappersKey) as? [UInt:_PKControlWrapper] 61 | } set { 62 | objc_setAssociatedObject(self, &UIControlAssociatedWrappersKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) 63 | } 64 | } 65 | 66 | func pk_addAction(for controlEvents: UIControl.Event, handler: @escaping (_ sender: UIControl) -> Void) { 67 | if pk_wrappers == nil { pk_wrappers = Dictionary() } 68 | let key: UInt = controlEvents.rawValue 69 | let target = _PKControlWrapper(handler: handler, controlEvents: controlEvents) 70 | pk_wrappers?.updateValue(target, forKey: key) 71 | self.addTarget(target, action: target.method, for: controlEvents) 72 | } 73 | 74 | func pk_hasAction(for controlEvents: UIControl.Event) -> Bool { 75 | guard let events = pk_wrappers else { return false } 76 | return events.keys.contains(controlEvents.rawValue) 77 | } 78 | 79 | func pk_removeAction(for controlEvents: UIControl.Event) { 80 | if var events = pk_wrappers, !events.isEmpty { 81 | for target in events.values { 82 | self.removeTarget(target, action: target.method, for: controlEvents) 83 | } 84 | events.removeValue(forKey: controlEvents.rawValue) 85 | if events.isEmpty { pk_wrappers = nil } 86 | } 87 | } 88 | } 89 | 90 | private class _PKControlWrapper { 91 | var block: ((_ sender: UIControl) -> Void)? 92 | let method = #selector(invoke(_:)) 93 | 94 | init(handler: @escaping (_ sender: UIControl) -> Void, controlEvents: UIControl.Event) { 95 | block = handler 96 | } 97 | 98 | @objc func invoke(_ sender: UIControl) { 99 | block?(sender) 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /OverlayController/Extensions/UIDevice+PKExtensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIDevice+PKExtensions.swift 3 | // PKExtensions 4 | // 5 | // Created by zhanghao on 2020/2/24. 6 | // Copyright © 2020 zhanghao. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | extension PKDeviceExtensions { 12 | 13 | /// 判断设备是否为iPhone 14 | static func isPhone() -> Bool { 15 | return UIDevice.current.userInterfaceIdiom == .phone 16 | } 17 | 18 | /// 判断设备是否为iPad 19 | static func isPad() -> Bool { 20 | return UIDevice.current.userInterfaceIdiom == .pad 21 | } 22 | 23 | /// 判断设备是否为模拟器 24 | static func isSimulator() -> Bool { 25 | #if targetEnvironment(simulator) 26 | return true 27 | #else 28 | return false 29 | #endif 30 | } 31 | 32 | /// 获取设备语言 33 | static func language() -> String { 34 | return Bundle.main.preferredLocalizations[0] 35 | } 36 | } 37 | 38 | public struct PKDeviceExtensions { 39 | fileprivate static var Base: UIDevice.Type { UIDevice.self } 40 | fileprivate var base: UIDevice 41 | fileprivate init(_ base: UIDevice) { self.base = base } 42 | } 43 | 44 | public extension UIDevice { 45 | var pk: PKDeviceExtensions { PKDeviceExtensions(self) } 46 | static var pk: PKDeviceExtensions.Type { PKDeviceExtensions.self } 47 | } 48 | -------------------------------------------------------------------------------- /OverlayController/Extensions/UIFont+PKExtensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIFont+PKExtensions.swift 3 | // PKExtensions 4 | // 5 | // Created by zhanghao on 2020/2/23. 6 | // Copyright © 2020 zhanghao. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | public extension PKFontExtensions { 12 | 13 | /// 系统字体族所有字体名称 14 | enum FontName: String { 15 | case academyEngravedLetPlain 16 | case alNile 17 | case americanTypewriter 18 | case appleColorEmoji 19 | case appleSDGothicNeo 20 | case arial 21 | case arialHebrew 22 | case arialMT 23 | case arialRoundedMTBold 24 | case avenir 25 | case avenirNext 26 | case avenirNextCondensed 27 | case baskerville 28 | case bodoniOrnamentsITCTT 29 | case bodoniSvtyTwoITCTT 30 | case bradleyHandITCTT 31 | case chalkboardSE 32 | case chalkduster 33 | case charter 34 | case cochin 35 | case copperplate 36 | case courier 37 | case courierNewPS 38 | case courierNewPSMT 39 | case dINAlternate 40 | case dINCondensed 41 | case damascus 42 | case devanagariSangamMN 43 | case didot 44 | case diwanMishafi 45 | case euphemiaUCAS 46 | case farah 47 | case geezaPro 48 | case georgia 49 | case gillSans 50 | case gujaratiSangamMN 51 | case gurmukhiMN 52 | case helvetica 53 | case helveticaNeue 54 | case hiraMaruProNW4 55 | case hiraMinProNW3 56 | case hiraMinProNW6 57 | case hiraginoSansW3 58 | case hiraginoSansW6 59 | case hoeflerText 60 | case kailasa 61 | case kannadaSangamMN 62 | case kefa 63 | case khmerSangamMN 64 | case kohinoorBangla 65 | case kohinoorDevanagari 66 | case kohinoorTelugu 67 | case laoSangamMN 68 | case malayalamSangamMN 69 | case markerFelt 70 | case menlo 71 | case myanmarSangamMN 72 | case noteworthy 73 | case notoNastaliqUrdu 74 | case notoSansChakma 75 | case optima 76 | case oriyaSangamMN 77 | case palatino 78 | case papyrus 79 | case partyLetPlain 80 | case pingFangHK 81 | case pingFangSC 82 | case pingFangTC 83 | case rockwell 84 | case savoyeLetPlain 85 | case sinhalaSangamMN 86 | case snellRoundhand 87 | case symbol 88 | case tamilSangamMN 89 | case thonburi 90 | case timesNewRomanPS 91 | case timesNewRomanPSMT 92 | case trebuchet 93 | case trebuchetMS 94 | case verdana 95 | case zapfDingbatsITC 96 | case zapfino 97 | } 98 | 99 | /// 系统字体族所有字体样式 100 | enum FontStyle: String { 101 | case none = "" 102 | case bold 103 | case boldItalic 104 | case boldOblique 105 | case italic 106 | case regular 107 | case oblique 108 | case roman 109 | case thin 110 | case wide 111 | case demiBold 112 | case demiBoldItalic 113 | case semiBold 114 | case semiBoldItalic 115 | case black 116 | case blackOblique 117 | case book 118 | case bookIt 119 | case bookIta 120 | case bookOblique 121 | case heavy 122 | case heavyItalic 123 | case heavyOblique 124 | case medium 125 | case mediumItalic 126 | case mediumOblique 127 | case boldMT 128 | case italicMT 129 | case boldItalicMT 130 | case condensed 131 | case condensedBold 132 | case condensedLight 133 | case light 134 | case lightOblique 135 | case ultraLight 136 | case ultraLightItalic 137 | case condensedMedium 138 | case condensedExtraBold 139 | } 140 | 141 | /// 返回对应名称及样式的字体,字体不存在则返回nil(可选值) 142 | /// 143 | /// - Parameters: 144 | /// - name: 字体名称 145 | /// - style: 字体样式 146 | /// - size: 字号大小 147 | /// - Returns: UIFont 148 | static func name(_ name: FontName, style: FontStyle = .none, size: CGFloat = UIFont.systemFontSize) -> UIFont? { 149 | let fontName = name.rawValue + "-" + style.rawValue 150 | if let font = UIFont(name: fontName, size: size) { return font } 151 | if let font = UIFont(name: name.rawValue, size: size) { return font } 152 | return UIFont(name: name.rawValue + "-" + FontStyle.regular.rawValue, size: size) 153 | } 154 | 155 | /// 返回对应名称及样式的字体,字体不存在则返回系统默认字体 156 | static func fontName(_ fontName: FontName, style: FontStyle = .none, size: CGFloat = UIFont.systemFontSize) -> UIFont { 157 | return name(fontName, style: style, size: size) ?? Base.systemFont(ofSize: size) 158 | } 159 | 160 | /// 返回像素值对应的字体大小 161 | static func pointsByPixel(_ pixel: CGFloat) -> CGFloat { 162 | switch UIDevice.current.userInterfaceIdiom { 163 | case .phone: 164 | return pixel * (8.0 / 15.0) 165 | default: 166 | return pixel * 0.5 167 | } 168 | } 169 | 170 | /// 返回当前字体,并根据像素值调整字体大小 171 | func fontWithPixel(_ pixel: CGFloat) -> UIFont { 172 | return base.withSize(UIFont.pk.pointsByPixel(pixel)) 173 | } 174 | 175 | /// 获取字体族所有字体名称 176 | static var entireFamilyNames: [String] { 177 | var names = [String]() 178 | for family in Base.familyNames { 179 | for name in Base.fontNames(forFamilyName: family) { names.append(name) } 180 | } 181 | return names.sorted(by: {$0 < $1}) 182 | } 183 | } 184 | 185 | public struct PKFontExtensions { 186 | fileprivate static var Base: UIFont.Type { UIFont.self } 187 | fileprivate var base: UIFont 188 | fileprivate init(_ base: UIFont) { self.base = base } 189 | } 190 | 191 | public extension UIFont { 192 | var pk: PKFontExtensions { PKFontExtensions(self) } 193 | static var pk: PKFontExtensions.Type { PKFontExtensions.self } 194 | } 195 | -------------------------------------------------------------------------------- /OverlayController/Extensions/UIGestureRecognizer+PKExtensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIGestureRecognizer+PKExtensions.swift 3 | // PKExtensions 4 | // 5 | // Created by zhanghao on 2020/2/24. 6 | // Copyright © 2020 zhanghao. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | public extension PKGestureRecognizerExtensions where Base: UIGestureRecognizer { 12 | 13 | /// 初始化手势识别器并添加闭包事件 14 | static func addGestureHandler(_ handler: @escaping (_ sender: UIGestureRecognizer) -> Void) -> Base { 15 | let owner = Base.init() 16 | owner.pk_addGesture(handler: handler) 17 | return owner 18 | } 19 | 20 | /// 为手势识别器并添加闭包事件 21 | func addGestureHandler(_ handler: @escaping (_ sender: UIGestureRecognizer) -> Void) { 22 | base.pk_addGesture(handler: handler) 23 | } 24 | 25 | /// 移除所有手势识别器闭包事件 26 | func removeGestureHandlers() { 27 | base.pk_removeGestureHandlers() 28 | } 29 | } 30 | 31 | private var UIGestureRecognizerAssociatedWrappersKey: Void? 32 | 33 | private extension UIGestureRecognizer { 34 | var pk_wrappers: [_PKGestureRecognizerWrapper]? { 35 | get { 36 | return objc_getAssociatedObject(self, &UIGestureRecognizerAssociatedWrappersKey) as? [_PKGestureRecognizerWrapper] 37 | } set { 38 | objc_setAssociatedObject(self, &UIGestureRecognizerAssociatedWrappersKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) 39 | } 40 | } 41 | 42 | func pk_addGesture(handler: @escaping (_ sender: UIGestureRecognizer) -> Void) { 43 | if pk_wrappers == nil { pk_wrappers = Array() } 44 | let target = _PKGestureRecognizerWrapper(handler: handler) 45 | pk_wrappers?.append(target) 46 | self.addTarget(target, action: target.method) 47 | } 48 | 49 | func pk_removeGestureHandlers() { 50 | if var events = pk_wrappers, !events.isEmpty { 51 | for target in events { 52 | self.removeTarget(target, action: target.method) 53 | } 54 | events.removeAll() 55 | pk_wrappers = nil 56 | } 57 | } 58 | } 59 | 60 | private class _PKGestureRecognizerWrapper { 61 | var block: ((_ sender: T) -> Void)? 62 | let method = #selector(invoke(_:)) 63 | 64 | init(handler: @escaping (_ sender: T) -> Void) { 65 | block = handler 66 | } 67 | 68 | @objc func invoke(_ sender: UIGestureRecognizer) { 69 | block?(sender as! T) 70 | } 71 | } 72 | 73 | public struct PKGestureRecognizerExtensions { 74 | fileprivate var base: Base 75 | fileprivate init(_ base: Base) { self.base = base } 76 | } 77 | 78 | public protocol PKGestureRecognizerExtensionsCompatible {} 79 | 80 | public extension PKGestureRecognizerExtensionsCompatible { 81 | static var pk: PKGestureRecognizerExtensions.Type { PKGestureRecognizerExtensions.self } 82 | var pk: PKGestureRecognizerExtensions { get{ PKGestureRecognizerExtensions(self) } set{} } 83 | } 84 | 85 | extension UIGestureRecognizer: PKGestureRecognizerExtensionsCompatible {} 86 | -------------------------------------------------------------------------------- /OverlayController/Extensions/UIKit+PKOverride.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIKit+PKOverride.swift 3 | // UIKit+PKOverride 4 | // 5 | // Created by zhanghao on 2020/2/27. 6 | // Copyright © 2020 zhanghao. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | // MARK: - PKInsetLabel 12 | 13 | /// 调整UILabel文本内边距 14 | public class PKInsetLabel: UILabel { 15 | 16 | /// 设置文本内边距 17 | public var textInsets: UIEdgeInsets = .zero 18 | 19 | override public func drawText(in rect: CGRect) { 20 | super.drawText(in: rect.inset(by: textInsets)) 21 | } 22 | 23 | override public func textRect(forBounds bounds: CGRect, limitedToNumberOfLines numberOfLines: Int) -> CGRect { 24 | let insets = textInsets 25 | var rect = super.textRect(forBounds: bounds.inset(by: insets), limitedToNumberOfLines: numberOfLines) 26 | rect.origin.x -= insets.left 27 | rect.origin.y -= insets.top 28 | rect.size.width += (insets.left + insets.right) 29 | rect.size.height += (insets.top + insets.bottom) 30 | return rect 31 | } 32 | } 33 | 34 | // MARK: - PKLegendButton 35 | 36 | /// 调整UIButton图片文字布局 37 | public class PKLegendButton: UIButton { 38 | 39 | /// 图片与文字布局位置 40 | public enum LayoutType { 41 | /// 图片在上,文字在下 42 | case top 43 | /// 图片在左,文字在右 44 | case left 45 | /// 图片在下,文字在上 46 | case bottom 47 | /// 图片在右,文字在左 48 | case right 49 | } 50 | 51 | /// 设置图片尺寸 52 | public var _imageSize: CGSize = .zero { 53 | didSet { 54 | guard _imageSize.width > 0, _imageSize.height > 0 else { return } 55 | _layoutNeeded = true 56 | setNeedsLayout() 57 | } 58 | } 59 | 60 | /// 更新布局位置并设置子视图间距 (调用前应确认设置了title和image) 61 | public func _update(layoutType type: LayoutType, itemSpacing: CGFloat = CGFloat.leastNormalMagnitude) { 62 | _spacing = itemSpacing 63 | _layoutNeeded = true 64 | _layoutType = type 65 | setNeedsLayout() 66 | } 67 | 68 | private var _layoutNeeded = false 69 | private var _layoutType = LayoutType.left 70 | private var _spacing: CGFloat = CGFloat.leastNormalMagnitude 71 | 72 | public override func layoutSubviews() { 73 | super.layoutSubviews() 74 | guard _layoutNeeded else { return } 75 | 76 | let gap = _spacing 77 | let imgSize = _imageSize.equalTo(.zero) ? imageView!.bounds.size : _imageSize 78 | var lbeSize = titleLabel?.intrinsicContentSize ?? .zero 79 | 80 | switch _layoutType { 81 | case .top: 82 | lbeSize.width = min(lbeSize.width, bounds.size.width) 83 | let contentHeight = imgSize.height + lbeSize.height + gap 84 | let padding = (bounds.height - contentHeight) * 0.5 85 | let imageX = (bounds.width - imgSize.width) * 0.5 86 | let labelX = (bounds.width - lbeSize.width) * 0.5 87 | let labelY = padding + imgSize.height + gap 88 | imageView?.frame = CGRect(x: imageX, y: padding, width: imgSize.width, height: imgSize.height) 89 | titleLabel?.frame = CGRect(x: labelX, y: labelY, width: lbeSize.width, height: lbeSize.height) 90 | case .left: 91 | let padding: CGFloat = 0 92 | let imageY = (bounds.height - imgSize.height) / 2 93 | let labelX = padding + imgSize.width + gap 94 | let labelY = (bounds.height - lbeSize.height) / 2 95 | imageView?.frame = CGRect(x: padding, y: imageY, width: imgSize.width, height: imgSize.height) 96 | titleLabel?.frame = CGRect(x: labelX, y: labelY, width: lbeSize.width, height: lbeSize.height) 97 | break 98 | case .bottom: 99 | let contentHeight = imgSize.height + lbeSize.height + gap 100 | let padding = (bounds.height - contentHeight) / 2 101 | let labelX = (bounds.width - lbeSize.width) / 2 102 | let imageX = (bounds.width - imgSize.width) / 2 103 | let imageY = padding + lbeSize.height + gap 104 | imageView?.frame = CGRect(x: imageX, y: imageY, width: imgSize.width, height: imgSize.height) 105 | titleLabel?.frame = CGRect(x: labelX, y: padding, width: lbeSize.width, height: lbeSize.height) 106 | break 107 | case .right: 108 | let contentWidth = imgSize.width + lbeSize.width + gap 109 | let padding = (bounds.width - contentWidth) / 2 110 | let labelY = (bounds.height - lbeSize.height) / 2; 111 | let imageX = padding + lbeSize.width + gap; 112 | let imageY = (bounds.height - imgSize.height) / 2; 113 | imageView?.frame = CGRect(x: imageX, y: imageY, width: imgSize.width, height: imgSize.height) 114 | titleLabel?.frame = CGRect(x: padding, y: labelY, width: lbeSize.width, height: lbeSize.height) 115 | break 116 | } 117 | } 118 | } 119 | 120 | // MARK: - PKTextView 121 | 122 | public class PKTextView: UITextView { 123 | 124 | /// 设置占位文本 125 | public var placeholder: String? { 126 | didSet { 127 | setNeedsDisplay() 128 | } 129 | } 130 | 131 | /// 设置占位文本颜色 132 | public var placeholderColor: UIColor? { 133 | didSet { 134 | setNeedsDisplay() 135 | } 136 | } 137 | 138 | /// 调整占位文本内边距 139 | var placeholderInsets: UIEdgeInsets = UIEdgeInsets(top: 8, left: 8, bottom: 0, right: 0) { 140 | didSet { 141 | setNeedsDisplay() 142 | } 143 | } 144 | 145 | public override init(frame: CGRect, textContainer: NSTextContainer?) { 146 | super.init(frame: frame, textContainer: textContainer) 147 | bindNotifications() 148 | } 149 | 150 | required init?(coder: NSCoder) { super.init(coder: coder) } 151 | 152 | public override func draw(_ rect: CGRect) { 153 | guard !hasText else { return } 154 | guard let textValue = placeholder else { return } 155 | let fontValue = font ?? UIFont.systemFont(ofSize: UIFont.systemFontSize) 156 | let colorValue = placeholderColor ?? UIColor.gray 157 | let attributedText = NSMutableAttributedString(string: textValue) 158 | let range = NSRange(location: 0, length: attributedText.length) 159 | attributedText.addAttribute(.font, value: fontValue, range: range) 160 | attributedText.addAttribute(.foregroundColor, value: colorValue, range: range) 161 | let rect = CGRect(x: placeholderInsets.left, 162 | y: placeholderInsets.top, 163 | width: bounds.width - placeholderInsets.left - placeholderInsets.right, 164 | height: bounds.height - placeholderInsets.top - placeholderInsets.bottom) 165 | attributedText.draw(in: rect) 166 | } 167 | 168 | public override func layoutSubviews() { 169 | super.layoutSubviews() 170 | setNeedsDisplay() 171 | } 172 | 173 | public override var font: UIFont? { 174 | get { 175 | return super.font 176 | } set { 177 | super.font = newValue 178 | setNeedsDisplay() 179 | } 180 | } 181 | 182 | public override var attributedText: NSAttributedString! { 183 | get { 184 | return super.attributedText 185 | } set { 186 | setNeedsDisplay() 187 | } 188 | } 189 | 190 | public override var text: String! { 191 | get { 192 | return super.text 193 | } set { 194 | setNeedsDisplay() 195 | } 196 | } 197 | 198 | private func bindNotifications() { 199 | NotificationCenter.default.addObserver(self, selector: #selector(textDidChange(_:)), name: UITextView.textDidChangeNotification, object: nil) 200 | } 201 | 202 | private func unbindNotifications() { 203 | NotificationCenter.default.removeObserver(self, name: UITextView.textDidChangeNotification, object: nil) 204 | } 205 | 206 | @objc func textDidChange(_ notif: Notification) { 207 | setNeedsDisplay() 208 | } 209 | 210 | deinit { 211 | unbindNotifications() 212 | } 213 | 214 | public override func deleteBackward() { 215 | super.deleteBackward() 216 | delegate?.textViewDidChange?(self) 217 | } 218 | } 219 | -------------------------------------------------------------------------------- /OverlayController/Extensions/UILabel+PKExtensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UILabel+PKExtensions.swift 3 | // PKExtensions 4 | // 5 | // Created by zhanghao on 2020/2/24. 6 | // Copyright © 2020 zhanghao. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | public extension PKViewExtensions where Base: UILabel { 12 | 13 | /// 初始化UILabel并设置字体/文本颜色/文本对齐方式 14 | static func make(font: UIFont = .systemFont(ofSize: UIFont.systemFontSize), color: UIColor, alignment: NSTextAlignment = .center) -> Base { 15 | let label = UILabel.init() 16 | label.font = font 17 | label.textColor = color 18 | label.textAlignment = alignment 19 | return label as! Base 20 | } 21 | 22 | /// 设置文本时显示渐变动画 23 | func set(text string: String?, duration: TimeInterval = 0.25) { 24 | let transition = CATransition() 25 | transition.duration = duration 26 | transition.type = .fade 27 | transition.subtype = .none 28 | base.layer.add(transition, forKey: nil) 29 | base.text = string 30 | } 31 | 32 | /// 获取自适应估算大小 33 | func estimatedSize(width: CGFloat = CGFloat.greatestFiniteMagnitude, height: CGFloat = CGFloat.greatestFiniteMagnitude) -> CGSize { 34 | base.sizeThatFits(CGSize(width: width, height: height)) 35 | } 36 | 37 | /// 获取自适应估算宽度 38 | func estimatedWidth() -> CGFloat { 39 | base.sizeThatFits(CGSize(width: CGFloat.greatestFiniteMagnitude, height: base.bounds.height)).width 40 | } 41 | 42 | /// 获取自适应估算高度 43 | func estimatedHeight() -> CGFloat { 44 | base.sizeThatFits(CGSize(width: base.bounds.width, height: CGFloat.greatestFiniteMagnitude)).height 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /OverlayController/Extensions/UIScreen+PKExtensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIScreen+PKExtensions.swift 3 | // PKExtensions 4 | // 5 | // Created by zhanghao on 2020/2/23. 6 | // Copyright © 2020 zhanghao. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | public extension PKScreenExtensions { 12 | 13 | /// 获取屏幕尺寸宽度 14 | static var width: CGFloat { 15 | return UIScreen.main.bounds.size.width 16 | } 17 | 18 | /// 获取屏幕尺寸高度 19 | static var height: CGFloat { 20 | return UIScreen.main.bounds.size.height 21 | } 22 | 23 | /// 获取屏幕尺寸 24 | static var size: CGSize { 25 | return UIScreen.main.bounds.size 26 | } 27 | 28 | /// 交换屏幕尺寸宽高 29 | static var swapSize: CGSize { 30 | return CGSize(width: UIScreen.main.bounds.height, height: UIScreen.main.bounds.width) 31 | } 32 | 33 | /// 获取当前屏幕的安全区域 34 | static var safeInsets: UIEdgeInsets { 35 | if #available(iOS 11.0, *) { 36 | return UIApplication.pk.keyWindow?.safeAreaInsets ?? UIEdgeInsets.zero 37 | } else { 38 | return UIEdgeInsets(top: 20, left: 0, bottom: 0, right: 0) 39 | } 40 | } 41 | 42 | /// 获取当前屏幕下状态栏高度 43 | static var statusBarHeight: CGFloat { 44 | return safeInsets.top 45 | } 46 | 47 | /// 获取导航栏+状态栏高度 48 | static var totalNavigationHeight: CGFloat { 49 | return 44 + safeInsets.top 50 | } 51 | 52 | /// 获取tabbar+底部安全区高度 53 | static var totalTabbarHeight: CGFloat { 54 | return 49 + safeInsets.bottom 55 | } 56 | } 57 | 58 | public extension PKScreenExtensions { 59 | 60 | enum ScreenType { 61 | case isIpadClassic 62 | case isIpadRetina 63 | case isIpadPro 64 | case isIphone4 // iphone 4/4s 65 | case isIphone5 // iphone 5/5c/5s/se 66 | case isIphone6 // iphone 6/6s/7/8 67 | case isIphone6p // iphone 6p/6sp/7p/8p 68 | case isIphoneX // iphone 11pro/x/xs/ 69 | case isIphoneXR // iphone 11/xr 70 | case isIphoneMax // iphone 11pro max/xs max 71 | } 72 | 73 | /// 判断当前设置类型 74 | static func type(_ type: ScreenType) -> Bool { 75 | let isIpad = UIDevice.current.userInterfaceIdiom == .phone 76 | let isIphone = UIDevice.current.userInterfaceIdiom == .phone 77 | let size: CGSize = UIScreen.main.bounds.size 78 | switch type { 79 | case .isIphone4: 80 | return isIphone && size.equalTo(CGSize(width: 320, height: 480)) 81 | case .isIphone5: 82 | return isIphone && size.equalTo(CGSize(width: 320, height: 568)) 83 | case .isIphone6: 84 | return isIphone && size.equalTo(CGSize(width: 375, height: 667)) 85 | case .isIphone6p: 86 | return isIphone && size.equalTo(CGSize(width: 414, height: 736)) 87 | case .isIphoneX: 88 | return isIphone && size.equalTo(CGSize(width: 375, height: 812)) 89 | case .isIphoneXR: 90 | guard isIphone, size.equalTo(CGSize(width: 414, height: 896)) else { 91 | return false 92 | } 93 | guard let mode = UIScreen.main.currentMode else { 94 | return false 95 | } 96 | return mode.size.equalTo(CGSize(width: 828, height: 1792)) 97 | case .isIphoneMax: 98 | guard isIphone, size.equalTo(CGSize(width: 414, height: 896)) else { 99 | return false 100 | } 101 | guard let mode = UIScreen.main.currentMode else { 102 | return false 103 | } 104 | return mode.size.equalTo(CGSize(width: 1242, height: 2688)) 105 | case .isIpadPro: 106 | return isIpad && (size.equalTo(CGSize(width: 1112, height: 834)) 107 | || size.equalTo(CGSize(width: 1366, height: 1024))) 108 | case .isIpadRetina: 109 | guard isIpad, size.equalTo(CGSize(width: 1024, height: 768)) else { 110 | return false 111 | } 112 | return UIScreen.main.scale == 1 113 | case .isIpadClassic: 114 | guard isIpad, size.equalTo(CGSize(width: 1024, height: 768)) else { 115 | return false 116 | } 117 | return UIScreen.main.scale != 1 118 | } 119 | } 120 | 121 | /// 判断当前设备是否为全面屏 (iphone 11/11pro/11pro max/x/xs/xs max/xr) 122 | static var isFull: Bool { 123 | if #available(iOS 11.0, *) { 124 | let _safe = UIApplication.pk.keyWindow?.safeAreaInsets ?? UIEdgeInsets.zero 125 | return (_safe.bottom > 0) ? true : false 126 | } 127 | return false 128 | } 129 | } 130 | 131 | public struct PKScreenExtensions { 132 | fileprivate static var Base: UIScreen.Type { UIScreen.self } 133 | fileprivate var base: UIScreen 134 | fileprivate init(_ base: UIScreen) { self.base = base } 135 | } 136 | 137 | public extension UIScreen { 138 | var pk: PKScreenExtensions { PKScreenExtensions(self) } 139 | static var pk: PKScreenExtensions.Type { PKScreenExtensions.self } 140 | } 141 | -------------------------------------------------------------------------------- /OverlayController/Extensions/UIViewController+PKExtensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIViewControllerExtensions.swift 3 | // AirTraffic_swift 4 | // 5 | // Created by zhanghao on 2020/2/26. 6 | // Copyright © 2020 zhanghao. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | public extension PKViewControllerExtensions where Base: UIViewController { 12 | 13 | /// 返回当前实例的类名称 14 | var className: String { 15 | return String(describing: type(of: base)) 16 | } 17 | 18 | /// 返回当前类名称 19 | static var className: String { 20 | return String(describing: Base.self) 21 | } 22 | 23 | /// 添加自控制器及其视图 24 | func addChild(_ childController: UIViewController, toView: UIView) { 25 | base.addChild(childController) 26 | toView.addSubview(childController.view) 27 | childController.didMove(toParent: base) 28 | } 29 | 30 | /// 添加背景图 31 | func addBackgroundImage(_ named: String) { 32 | return addBackgroundImage(UIImage(named: named)) 33 | } 34 | 35 | /// 添加背景图 36 | func addBackgroundImage(_ image: UIImage?) { 37 | guard let img = image else { return } 38 | let imageView = UIImageView(frame: base.view.bounds) 39 | imageView.contentMode = .scaleAspectFill 40 | imageView.image = img 41 | imageView.clipsToBounds = true 42 | base.view.addSubview(imageView) 43 | base.view.sendSubviewToBack(imageView) 44 | } 45 | } 46 | 47 | public struct PKViewControllerExtensions { 48 | fileprivate var base: Base 49 | fileprivate init(_ base: Base) { self.base = base } 50 | } 51 | 52 | public protocol PKViewControllerExtensionsCompatible {} 53 | 54 | public extension PKViewControllerExtensionsCompatible { 55 | static var pk: PKViewControllerExtensions.Type { PKViewControllerExtensions.self } 56 | var pk: PKViewControllerExtensions { get{ PKViewControllerExtensions(self) } set{} } 57 | } 58 | 59 | extension UIViewController: PKViewControllerExtensionsCompatible {} 60 | -------------------------------------------------------------------------------- /OverlayController/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UIApplicationSceneManifest 24 | 25 | UIApplicationSupportsMultipleScenes 26 | 27 | UISceneConfigurations 28 | 29 | UIWindowSceneSessionRoleApplication 30 | 31 | 32 | UISceneConfigurationName 33 | Default Configuration 34 | UISceneDelegateClassName 35 | $(PRODUCT_MODULE_NAME).SceneDelegate 36 | UISceneStoryboardFile 37 | Main 38 | 39 | 40 | 41 | 42 | UILaunchStoryboardName 43 | LaunchScreen 44 | UIMainStoryboardFile 45 | Main 46 | UIRequiredDeviceCapabilities 47 | 48 | armv7 49 | 50 | UISupportedInterfaceOrientations 51 | 52 | UIInterfaceOrientationPortrait 53 | UIInterfaceOrientationLandscapeLeft 54 | UIInterfaceOrientationLandscapeRight 55 | 56 | UISupportedInterfaceOrientations~ipad 57 | 58 | UIInterfaceOrientationPortrait 59 | UIInterfaceOrientationPortraitUpsideDown 60 | UIInterfaceOrientationLandscapeLeft 61 | UIInterfaceOrientationLandscapeRight 62 | 63 | UIViewControllerBasedStatusBarAppearance 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /OverlayController/SceneDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SceneDelegate.swift 3 | // OverlayController 4 | // 5 | // Created by ahong on 2020/2/26. 6 | // Copyright © 2020 zhanghao. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class SceneDelegate: UIResponder, UIWindowSceneDelegate { 12 | 13 | var window: UIWindow? 14 | 15 | 16 | @available(iOS 13.0, *) 17 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { 18 | // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. 19 | // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. 20 | // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). 21 | guard let _ = (scene as? UIWindowScene) else { return } 22 | } 23 | 24 | @available(iOS 13.0, *) 25 | func sceneDidDisconnect(_ scene: UIScene) { 26 | // Called as the scene is being released by the system. 27 | // This occurs shortly after the scene enters the background, or when its session is discarded. 28 | // Release any resources associated with this scene that can be re-created the next time the scene connects. 29 | // The scene may re-connect later, as its session was not neccessarily discarded (see `application:didDiscardSceneSessions` instead). 30 | } 31 | 32 | @available(iOS 13.0, *) 33 | func sceneDidBecomeActive(_ scene: UIScene) { 34 | // Called when the scene has moved from an inactive state to an active state. 35 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. 36 | } 37 | 38 | @available(iOS 13.0, *) 39 | func sceneWillResignActive(_ scene: UIScene) { 40 | // Called when the scene will move from an active state to an inactive state. 41 | // This may occur due to temporary interruptions (ex. an incoming phone call). 42 | } 43 | 44 | @available(iOS 13.0, *) 45 | func sceneWillEnterForeground(_ scene: UIScene) { 46 | // Called as the scene transitions from the background to the foreground. 47 | // Use this method to undo the changes made on entering the background. 48 | } 49 | 50 | @available(iOS 13.0, *) 51 | func sceneDidEnterBackground(_ scene: UIScene) { 52 | // Called as the scene transitions from the foreground to the background. 53 | // Use this method to save data, release shared resources, and store enough scene-specific state information 54 | // to restore the scene back to its current state. 55 | 56 | // Save changes in the application's managed object context when the application transitions to the background. 57 | (UIApplication.shared.delegate as? AppDelegate)?.saveContext() 58 | } 59 | 60 | 61 | } 62 | 63 | -------------------------------------------------------------------------------- /OverlayController/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // OverlayController 4 | // 5 | // Created by ahong on 2020/2/26. 6 | // Copyright © 2020 zhanghao. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ViewController: UIViewController { 12 | 13 | override func viewDidLoad() { 14 | super.viewDidLoad() 15 | // Do any additional setup after loading the view. 16 | setup() 17 | } 18 | 19 | override var preferredStatusBarStyle: UIStatusBarStyle { 20 | return .lightContent 21 | } 22 | 23 | func setup() { 24 | let size = navigationController?.navigationBar.bounds.size 25 | let fromColor = UIColor.pk.hex("0xDE5B6D")! 26 | let toColor = UIColor.pk.hex("0xF2A490")! 27 | let image = UIImage.pk.gradientImage(with: [fromColor, toColor], direction: .leftBottomToRightTop, size: size!) 28 | navigationController?.navigationBar.setBackgroundImage(image, for: .default) 29 | navigationController?.navigationBar.shadowImage = UIImage() 30 | navigationController?.navigationBar.tintColor = .white 31 | 32 | title = "OverlayController" 33 | var titleAttributes = [NSAttributedString.Key:Any]() 34 | titleAttributes.updateValue(UIColor.white, forKey: .foregroundColor) 35 | titleAttributes.updateValue(UIFont.pk.fontName(.gillSans, style: .semiBoldItalic, size: 22), forKey: .font) 36 | navigationController?.navigationBar.titleTextAttributes = titleAttributes 37 | 38 | let backItem = UIBarButtonItem() 39 | var backAttributes = [NSAttributedString.Key:Any]() 40 | backAttributes.updateValue(UIColor.white, forKey: .foregroundColor) 41 | backAttributes.updateValue(UIFont.pk.fontName(.gillSans, style: .semiBoldItalic, size: 20), forKey: .font) 42 | backItem.setTitleTextAttributes(backAttributes, for: .normal) 43 | backItem.setTitleTextAttributes(backAttributes, for: .highlighted) 44 | backItem.title = "back" 45 | navigationItem.backBarButtonItem = backItem 46 | 47 | let button = UIButton(type: .custom) 48 | button.layer.masksToBounds = true 49 | button.layer.cornerRadius = 2 50 | button.setTitle("Next", for: .normal) 51 | button.setTitleColor(.white, for: .normal) 52 | button.frame = CGRect(origin: .zero, size: CGSize(width: 90, height: 40)) 53 | button.center = CGPoint(x: view.bounds.width / 2, y: 270) 54 | button.titleLabel?.font = UIFont.pk.fontName(.gillSans, style: .semiBoldItalic, size: 17) 55 | button.pk.addGradient(colors: [fromColor, toColor], direction: .leftTopToRightBottom) 56 | button.pk.addAction(for: .touchUpInside) { [unowned self] (_) in 57 | let vc = ExampleViewController() 58 | self.navigationController?.pushViewController(vc, animated: true) 59 | } 60 | button.setBackgroundImage(image, for: .normal) 61 | view.addSubview(button) 62 | } 63 | } 64 | 65 | -------------------------------------------------------------------------------- /OverlayController/view/OverlayBalloonView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OverlayBalloonView.swift 3 | // OverlayController 4 | // 5 | // Created by zhanghao on 2020/2/15. 6 | // Copyright © 2020 zhanghao. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @objc public protocol OverlayBalloonViewDelegate { 12 | 13 | @objc optional func overlayBalloonView(_ balloonView: OverlayBalloonView, didSelectItemAt index: Int) 14 | } 15 | 16 | public class OverlayBalloonView: UIView, UIScrollViewDelegate { 17 | 18 | public weak var delegate: OverlayBalloonViewDelegate? 19 | 20 | public struct Data { 21 | var image: UIImage? 22 | var title: String? 23 | } 24 | 25 | public struct Layout { 26 | /// 需要显示多少页 27 | var pages: Int = 2 28 | /// 每页显示多少行 29 | var rows: Int = 2 30 | /// 每行显示多少个 31 | var rowCount: Int = 4 32 | } 33 | 34 | public private(set) var dataList: [Data]! 35 | public private(set) var layout: Layout! 36 | private var scrollView: UIScrollView! 37 | private var forward: UIButton! 38 | private var closeImage: UIImageView! 39 | 40 | override init(frame: CGRect) { 41 | super.init(frame: frame) 42 | forward = UIButton(type: .custom) 43 | forward.isUserInteractionEnabled = false 44 | forward.backgroundColor = UIColor.white 45 | forward.addTarget(self, action: #selector(forwardClicked), for: .touchUpInside) 46 | addSubview(forward) 47 | 48 | closeImage = UIImageView(image: UIImage(named: "sina_关闭")) 49 | forward.addSubview(closeImage) 50 | 51 | scrollView = UIScrollView() 52 | scrollView.bounces = false 53 | scrollView.showsHorizontalScrollIndicator = false 54 | scrollView.isPagingEnabled = true 55 | scrollView.delaysContentTouches = true 56 | scrollView.delegate = self 57 | self.addSubview(scrollView) 58 | } 59 | 60 | required init?(coder: NSCoder) { super.init(coder: coder) } 61 | 62 | public func update(layout: Layout, data: [Data]) { 63 | self.layout = layout 64 | self.dataList = data 65 | 66 | let forwardHeight = UIScreen.pk.safeInsets.bottom + 45 67 | forward.frame = CGRect(x: 0, y: bounds.height - forwardHeight, width: bounds.width, height: forwardHeight) 68 | closeImage.frame = CGRect(x: 0, y: 10, width: 25, height: 25) 69 | closeImage.centerX = forward.centerX 70 | 71 | let itemSize = CGSize(width: 60, height: 115) 72 | let gapv: CGFloat = 15 73 | let gapl = (bounds.width - CGFloat(layout.rowCount) * itemSize.width) / CGFloat(layout.rowCount + 1) 74 | let height = CGFloat(layout.rows) * itemSize.height + gapv + 60 75 | let top = bounds.height - forward.bounds.height - height 76 | scrollView.frame = CGRect(x: 0, y: top, width: bounds.width, height: height) 77 | scrollView.contentSize = CGSize(width: CGFloat(layout.pages) * bounds.width, height: height) 78 | 79 | for page in 0.. Void)?) { 127 | let pageView = scrollView.subviews[currentPage] 128 | for (index, aView) in pageView.subviews.enumerated() { 129 | UIView.animate(withDuration: 0.25, delay: 0.025 * Double(pageView.subviews.count - index), options: .curveEaseOut, animations: { 130 | aView.alpha = 0 131 | aView.transform = CGAffineTransform(translationX: 0, y: CGFloat(self.layout.rows) * aView.bounds.height) 132 | }, completion: { (finished: Bool) in 133 | if (index == pageView.subviews.count - 1) { ended?(finished) } 134 | }) 135 | } 136 | 137 | guard currentPage == 0 else { return } 138 | UIView.animate(withDuration: 0.5) { 139 | self.closeImage.transform = .identity 140 | } 141 | } 142 | 143 | @objc private func cellClicked(_ g: UITapGestureRecognizer) { 144 | let keyframeAnim = CAKeyframeAnimation(keyPath: "transform.scale") 145 | keyframeAnim.duration = 0.35 146 | keyframeAnim.values = [0.9, 1.5, 1] 147 | (g.view as! UIButton).imageView?.layer.add(keyframeAnim, forKey: nil) 148 | 149 | if layout.rows * layout.rowCount - 1 == g.view!.tag { 150 | scrollView.setContentOffset(CGPoint(x: bounds.width, y: 0), animated: true) 151 | } else { 152 | g.view?.isUserInteractionEnabled = false 153 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.00) { 154 | g.view?.isUserInteractionEnabled = true 155 | self.delegate?.overlayBalloonView?(self, didSelectItemAt: g.view!.tag) 156 | } 157 | } 158 | } 159 | 160 | @objc private func forwardClicked() { 161 | scrollView.setContentOffset(.zero, animated: true) 162 | } 163 | 164 | private var currentPage: Int = 0 165 | public func scrollViewDidScroll(_ scrollView: UIScrollView) { 166 | let page = Int(scrollView.contentOffset.x / bounds.width + 0.5) 167 | if currentPage != page { 168 | currentPage = page 169 | forward.isUserInteractionEnabled = currentPage > 0 170 | closeImage.image = UIImage(named: (currentPage > 0) ? "sina_返回" : "sina_关闭") 171 | } 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /OverlayController/view/OverlayPickerView.swift: -------------------------------------------------------------------------------- 1 | 2 | // 3 | // OverlayPickerView.swift 4 | // OverlayController 5 | // 6 | // Created by zhanghao on 2020/2/17. 7 | // Copyright © 2020 zhanghao. All rights reserved. 8 | // 9 | 10 | import UIKit 11 | 12 | @objc public protocol OverlayPickerViewDelegate { 13 | 14 | @objc optional func overlayPickerViewDidClickedSubmit(_ pickerView: OverlayPickerView) 15 | } 16 | 17 | public class OverlayPickerView: UIView, UIPickerViewDataSource, UIPickerViewDelegate { 18 | 19 | public weak var delegate: OverlayPickerViewDelegate? 20 | public private(set) var currentDate: Date? 21 | 22 | private var pickerView: UIPickerView! 23 | private var titleLabel: UILabel! 24 | private var submitBtn: UIButton! 25 | 26 | override init(frame: CGRect) { 27 | super.init(frame: frame) 28 | pickerView = UIPickerView() 29 | pickerView.dataSource = self 30 | pickerView.delegate = self 31 | addSubview(pickerView) 32 | 33 | titleLabel = UILabel() 34 | titleLabel.textAlignment = .left 35 | titleLabel.adjustsFontSizeToFitWidth = true 36 | titleLabel.font = UIFont.pk.name(.gillSans, style: .boldItalic, size: 16) 37 | titleLabel.textColor = UIColor.pk.rgba(red: 109, green: 222, blue: 67) 38 | addSubview(titleLabel) 39 | 40 | submitBtn = UIButton(type: .custom) 41 | submitBtn.setTitle("OK", for: .normal) 42 | submitBtn.titleLabel?.font = UIFont.pk.name(.gillSans, style: .boldItalic, size: 15) 43 | submitBtn.backgroundColor = UIColor.pk.rgba(red: 109, green: 222, blue: 67) 44 | submitBtn.setTitleColor(.white, for: .normal) 45 | submitBtn.layer.masksToBounds = true 46 | submitBtn.layer.cornerRadius = 2 47 | submitBtn.pk.addAction(for: .touchUpInside) { [unowned self] (_) in 48 | self.delegate?.overlayPickerViewDidClickedSubmit?(self) 49 | } 50 | addSubview(submitBtn) 51 | } 52 | 53 | required init?(coder: NSCoder) { super.init(coder: coder) } 54 | 55 | override public func layoutSubviews() { 56 | super.layoutSubviews() 57 | let padding: CGFloat = 20 58 | let btnw: CGFloat = 55, btnh: CGFloat = 25 59 | submitBtn.frame = CGRect(x: bounds.width - btnw - padding, y: 8, width: btnw, height: btnh) 60 | titleLabel.frame = CGRect(x: padding, y: 10, width: bounds.width - btnw - padding*2 - 10, height: btnh) 61 | pickerView.frame = CGRect(x: 0, y: titleLabel.frame.maxY, width: bounds.width, height: 240) 62 | } 63 | 64 | public override func sizeThatFits(_ size: CGSize) -> CGSize { 65 | layoutIfNeeded() 66 | return CGSize(width: size.width, height: pickerView.frame.maxY + UIScreen.pk.safeInsets.bottom + 50) 67 | } 68 | 69 | public func numberOfComponents(in pickerView: UIPickerView) -> Int { 70 | return allValues?.count ?? 0 71 | } 72 | 73 | public func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int { 74 | return allValues?[component].count ?? 0 75 | } 76 | 77 | public func pickerView(_ pickerView: UIPickerView, rowHeightForComponent component: Int) -> CGFloat { 78 | return 50 79 | } 80 | 81 | public func pickerView(_ pickerView: UIPickerView, viewForRow row: Int, forComponent component: Int, reusing view: UIView?) -> UIView { 82 | let pickerLabel = UILabel() 83 | pickerLabel.textColor = .black 84 | pickerLabel.textAlignment = .center 85 | pickerLabel.font = UIFont.pk.name(.gillSans, style: .boldItalic, size: 15) 86 | pickerLabel.text = allValues?[component][row].title 87 | let line1 = pickerView.subviews[safe: 1] 88 | let line2 = pickerView.subviews[safe: 2] 89 | line1?.backgroundColor = UIColor.black.withAlphaComponent(0.1) 90 | line2?.backgroundColor = UIColor.black.withAlphaComponent(0.1) 91 | line1?.height = 1 / UIScreen.main.scale 92 | line2?.height = 1 / UIScreen.main.scale 93 | return pickerLabel 94 | } 95 | 96 | public func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) { 97 | let item = allValues![component][row] 98 | let type = OverlayPickerData.TimeType(rawValue: component) 99 | 100 | switch type { 101 | case .year: 102 | year = item.rawValue 103 | let date = Date.pk.date(fromString: "\(year)-\(month)", format: "yyyy-MM") 104 | reloadDayComponent(date!) 105 | case .month: 106 | month = item.rawValue 107 | let date = Date.pk.date(fromString: "\(year)-\(month)", format: "yyyy-MM") 108 | reloadDayComponent(date!) 109 | case .day: 110 | day = item.rawValue 111 | case .hour: 112 | hour = item.rawValue 113 | case .minute: 114 | minute = item.rawValue 115 | default: break 116 | } 117 | 118 | let str = "\(year)-\(month)-\(day) \(hour):\(minute)" 119 | let date = Date.pk.date(fromString: str, format: "yyyy-MM-dd HH:mm")! 120 | reloadTitle(date) 121 | currentDate = date 122 | } 123 | 124 | private var data: OverlayPickerData = OverlayPickerData() 125 | private var allValues: [[OverlayPickerData.Item]]? 126 | private var year: Int = 0, month: Int = 0, day: Int = 0, hour: Int = 0, minute: Int = 0 127 | 128 | public func reloadAllComponents(_ date: Date) { 129 | currentDate = date 130 | reloadTitle(date) 131 | data.make(days: date.pk.numberOfDaysInMonth()) 132 | allValues = [data.years, data.months, data.days, data.hours, data.minutes] 133 | pickerView.reloadAllComponents() 134 | let comps = date.pk.dateComponents() 135 | year = comps.year! 136 | month = comps.month! 137 | day = comps.day! 138 | hour = comps.hour! 139 | minute = comps.minute! 140 | let keys = [year, month, day, hour, minute] 141 | for (column, arr) in allValues!.enumerated() { 142 | let index = arr.firstIndex(where: { $0.rawValue == keys[column] }) 143 | pickerView.selectRow(index!, inComponent: column, animated: true) 144 | } 145 | } 146 | 147 | private func reloadDayComponent(_ date: Date) { 148 | data.make(days: date.pk.numberOfDaysInMonth()) 149 | allValues = [data.years, data.months, data.days, data.hours, data.minutes] 150 | let index = data.days.firstIndex(where: { $0.rawValue == day }) ?? (data.days.count - 1) 151 | pickerView.reloadComponent(2) 152 | day = data.days[index].rawValue 153 | } 154 | 155 | private func reloadTitle(_ date: Date) { 156 | let weekday = date.pk.describeWeekday().shortChinese 157 | let comps = date.pk.dateComponents() 158 | let title = "\(comps.year!)年\(comps.month!)月\(comps.day!)日 \(weekday) \(comps.hour!)点\(comps.minute!)分" 159 | titleLabel.text = title 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /OverlayController/view/OverlayPublishView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OverlayPublishView.swift 3 | // OverlayController 4 | // 5 | // Created by iOS on 2020/2/15. 6 | // Copyright © 2020 zhanghao. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | public class OverlayPublishView: UIView { 12 | 13 | private var blurView: UIVisualEffectView! 14 | private var messagesLeft: OverlayPublishCell! 15 | private var messagesRight: OverlayPublishCell! 16 | private var dateLabel: UILabel! 17 | private var weekLabel: UILabel! 18 | private var draftLabel: UILabel! 19 | 20 | override init(frame: CGRect) { 21 | super.init(frame: frame) 22 | blurView = UIVisualEffectView(effect: UIBlurEffect(style: .extraLight)) 23 | addSubview(blurView) 24 | 25 | dateLabel = UILabel() 26 | dateLabel.font = UIFont.pk.fontName(.pingFangTC, style: .bold, size: 44) 27 | dateLabel.textColor = .black 28 | addSubview(dateLabel) 29 | 30 | weekLabel = UILabel() 31 | weekLabel.numberOfLines = 0 32 | weekLabel.font = UIFont.pk.fontName(.pingFangSC, style: .light, size: 12) 33 | weekLabel.textColor = UIColor.black.withAlphaComponent(0.8) 34 | addSubview(weekLabel) 35 | 36 | messagesLeft = OverlayPublishCell() 37 | messagesLeft.backgroundColor = UIColor.pk.rgba(red: 209, green: 240, blue: 215) 38 | messagesLeft.layer.cornerRadius = 5 39 | messagesLeft.layer.masksToBounds = true 40 | addSubview(messagesLeft) 41 | 42 | messagesRight = OverlayPublishCell() 43 | messagesRight.backgroundColor = UIColor.pk.rgba(red: 250, green: 227, blue: 213) 44 | messagesRight.layer.cornerRadius = 5 45 | messagesRight.layer.masksToBounds = true 46 | addSubview(messagesRight) 47 | 48 | draftLabel = UILabel() 49 | draftLabel.textAlignment = .center 50 | draftLabel.textColor = UIColor.black.withAlphaComponent(0.7) 51 | draftLabel.font = UIFont.pk.fontName(.pingFangSC, style: .light, size: 14) 52 | addSubview(draftLabel) 53 | 54 | update() 55 | } 56 | 57 | required init?(coder: NSCoder) { super.init(coder: coder) } 58 | 59 | public func update() { 60 | let date = Date() 61 | dateLabel.text = date.pk.toString(format: "dd") 62 | let text = date.pk.describeWeekday().chinese + "\n" + date.pk.toString(format: "MM/yyyy") 63 | let attriText = NSMutableAttributedString(string: text) 64 | let paragraphStyle = NSMutableParagraphStyle() 65 | paragraphStyle.lineSpacing = 2 66 | attriText.addAttribute(NSAttributedString.Key.paragraphStyle, value: paragraphStyle, range: NSRange(location: 0, length: text.count)) 67 | weekLabel.attributedText = attriText 68 | draftLabel.text = "草稿箱" 69 | 70 | blurView.frame = bounds 71 | let paddingLeft: CGFloat = 15, gap: CGFloat = 35 72 | let messageWidth = (bounds.width - paddingLeft * 2 - gap) / 2 73 | messagesLeft.frame = CGRect(x: paddingLeft, y: 0, width: messageWidth, height: 230) 74 | messagesLeft.center.y = UIScreen.main.bounds.height / 2 + 30 75 | messagesRight.frame = CGRect(x: messagesLeft.frame.maxX + gap, y: messagesLeft.frame.minY, width: messagesLeft.frame.width, height: messagesLeft.frame.height) 76 | draftLabel.frame = CGRect(x: 0, y: messagesLeft.frame.maxY + 70, width: 120, height: 50) 77 | draftLabel.center.x = UIScreen.main.bounds.width / 2 78 | 79 | let size = dateLabel.sizeThatFits(CGSize(width: 40, height: 40)) 80 | dateLabel.frame = CGRect.init(x: 15, y: 65, width: size.width, height: size.height) 81 | 82 | let _size = weekLabel.sizeThatFits(CGSize(width: 100, height: 40)) 83 | weekLabel.frame = CGRect(x: dateLabel.frame.maxX + 10, y: 0, width: _size.width, height: _size.height) 84 | weekLabel.center.y = dateLabel.center.y 85 | } 86 | 87 | public func presentAnimate() { 88 | messagesLeft.update1() 89 | messagesRight.update2() 90 | popupAnimate(targetView: messagesLeft) 91 | popupAnimate(targetView: messagesRight, delay: 0.1) 92 | } 93 | 94 | public func dismissAnimate() { 95 | dismissAnimate(targetView: messagesLeft) 96 | dismissAnimate(targetView: messagesRight) 97 | } 98 | 99 | private func popupAnimate(targetView: UIView, delay: TimeInterval = 0) { 100 | dateLabel.alpha = 0 101 | weekLabel.alpha = 0 102 | draftLabel.transform = CGAffineTransform(scaleX: 0.15, y: 0.15) 103 | targetView.transform = CGAffineTransform(scaleX: 0.15, y: 0.15) 104 | 105 | UIView.animate(withDuration:0.75, delay: 0.05 + delay, usingSpringWithDamping: 0.6, initialSpringVelocity: 0.25, options: .curveLinear, animations: { 106 | self.dateLabel.alpha = 1 107 | self.weekLabel.alpha = 1 108 | self.draftLabel.transform = CGAffineTransform.identity 109 | targetView.transform = CGAffineTransform.identity 110 | }) { (_) in } 111 | 112 | for index in 0.. CGSize { 99 | layoutIfNeeded() 100 | return CGSize(width: size.width, height: bottomView.frame.maxY) 101 | } 102 | 103 | public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 104 | return dataList?.count ?? 0 105 | } 106 | 107 | public func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { 108 | return layout.rowHeight 109 | } 110 | 111 | public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 112 | var cell = tableView.dequeueReusableCell(withIdentifier: "cell") as? OverlayShareViewCell 113 | if cell == nil { 114 | cell = OverlayShareViewCell(style: .default, reuseIdentifier: "cell") 115 | cell?.proxy = self 116 | cell?.sectionIndex = indexPath.row 117 | cell?.update(layout: layout, dataList: dataList?[indexPath.row]) 118 | } 119 | return cell! 120 | } 121 | 122 | public func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) { 123 | guard let arr = dataList else { return } 124 | (cell as! OverlayShareViewCell).separatorLine.isHidden = (indexPath.row == arr.count - 1) 125 | } 126 | 127 | @objc func didClickedCancel(_ sender: UIButton) { 128 | delegate?.overlayShareViewDidClickedCancel?(self) 129 | } 130 | } 131 | 132 | private let cellIdentifier: String = "OverlayShareViewCell" 133 | 134 | fileprivate class OverlayShareViewCell: UITableViewCell, UICollectionViewDataSource, UICollectionViewDelegate { 135 | 136 | var collectionLayout = UICollectionViewFlowLayout() 137 | var collectionView: UICollectionView! 138 | var separatorLine: UIView! 139 | var layout: OverlayShareView.Layout! 140 | var dataList: [OverlayShareView.Data]? 141 | var sectionIndex: Int! 142 | weak var proxy: OverlayShareView? 143 | 144 | override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { 145 | super.init(style: style, reuseIdentifier: reuseIdentifier) 146 | backgroundColor = .clear 147 | selectionStyle = .none 148 | collectionLayout.scrollDirection = .horizontal 149 | collectionView = UICollectionView(frame: self.bounds, collectionViewLayout: collectionLayout) 150 | collectionView.backgroundColor = backgroundColor 151 | collectionView.alwaysBounceHorizontal = true 152 | collectionView.showsHorizontalScrollIndicator = false 153 | collectionView.bounces = true 154 | collectionView.dataSource = self 155 | collectionView.delegate = self 156 | collectionView.register(OverlayShareViewItemCell.self, forCellWithReuseIdentifier: cellIdentifier) 157 | addSubview(collectionView) 158 | 159 | separatorLine = UIView() 160 | separatorLine.backgroundColor = UIColor.gray.withAlphaComponent(0.2) 161 | addSubview(separatorLine) 162 | } 163 | 164 | required init?(coder: NSCoder) { super.init(coder: coder) } 165 | 166 | func update(layout: OverlayShareView.Layout, dataList: [OverlayShareView.Data]?) { 167 | self.layout = layout 168 | collectionLayout.minimumInteritemSpacing = 0 169 | collectionLayout.minimumLineSpacing = layout.horizontalItemSpacing 170 | let height = layout.rowHeight - layout.sectionInset.top - layout.sectionInset.bottom 171 | collectionLayout.itemSize = CGSize(width: layout.itemWidth, height: height) 172 | collectionLayout.sectionInset = layout.sectionInset 173 | self.dataList = dataList 174 | collectionView.reloadData() 175 | } 176 | 177 | override func layoutSubviews() { 178 | super.layoutSubviews() 179 | collectionView.frame = bounds 180 | separatorLine.frame = CGRect(x: 15, y: bounds.height, width: bounds.width, height: 1 / UIScreen.main.scale) 181 | } 182 | 183 | func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { 184 | return dataList?.count ?? 0 185 | } 186 | 187 | func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { 188 | let cell: OverlayShareViewItemCell = collectionView.dequeueReusableCell(withReuseIdentifier: cellIdentifier, for: indexPath) as! OverlayShareViewItemCell 189 | cell.image.tag = indexPath.item 190 | cell.imageClickedClosure = { [unowned self] (idx) in 191 | let path = IndexPath(item: idx, section: self.sectionIndex) 192 | self.proxy?.delegate?.overlayShareView?(self.proxy!, didSelectItemAt: path) 193 | } 194 | cell.update(data: dataList?[indexPath.item], interitemSpacing: layout.verticalItemSpacing) 195 | return cell 196 | } 197 | } 198 | 199 | fileprivate class OverlayShareViewItemCell: UICollectionViewCell { 200 | 201 | let image = UIButton(type: .custom) 202 | let message = UILabel() 203 | var imageClickedClosure: ((Int) -> ())? 204 | 205 | override init(frame: CGRect) { 206 | super.init(frame: frame) 207 | image.isUserInteractionEnabled = true 208 | image.clipsToBounds = false 209 | image.layer.masksToBounds = true 210 | image.addTarget(self, action: #selector(imageClicked(_:)), for: .touchUpInside) 211 | addSubview(image) 212 | 213 | message.isUserInteractionEnabled = false 214 | message.textColor = UIColor.black.withAlphaComponent(0.5) 215 | message.font = UIFont.systemFont(ofSize: 10) 216 | message.textAlignment = .center 217 | message.numberOfLines = 0 218 | addSubview(message) 219 | } 220 | 221 | required init?(coder: NSCoder) { super.init(coder: coder) } 222 | 223 | func update(data: OverlayShareView.Data?, interitemSpacing: CGFloat) { 224 | image.setImage(data?.image, for: .normal) 225 | message.text = data?.title 226 | 227 | let padding: CGFloat = 6, btnWidth = bounds.width - padding 228 | image.frame = CGRect(x: padding / 2, y: 0, width: btnWidth, height: btnWidth) 229 | 230 | guard let text = message.text, text.count > 0 else { return } 231 | let heightLimit = bounds.height - image.frame.height - interitemSpacing 232 | var size = message.sizeThatFits(CGSize(width: bounds.width, 233 | height: CGFloat.greatestFiniteMagnitude)) 234 | size.height = min(size.height, heightLimit) 235 | size.width = bounds.width 236 | message.frame = CGRect(origin: CGPoint(x: 0, y: btnWidth + interitemSpacing), size: size) 237 | } 238 | 239 | @objc func imageClicked(_ sender: UIButton) { 240 | imageClickedClosure?(sender.tag) 241 | } 242 | } 243 | -------------------------------------------------------------------------------- /OverlayController/view/OverlaySidebarView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OverlaySidebarView.swift 3 | // OverlayController 4 | // 5 | // Created by zhanghao on 2020/2/15. 6 | // Copyright © 2020 zhanghao. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | public class OverlaySidebarView: UIView { 12 | 13 | public struct Data { 14 | var image: UIImage? 15 | var title: String? 16 | } 17 | 18 | private var blurView: UIVisualEffectView! 19 | private var legendViews: UIView! 20 | 21 | override init(frame: CGRect) { 22 | super.init(frame: frame) 23 | blurView = UIVisualEffectView(effect: UIBlurEffect(style: .dark)) 24 | addSubview(blurView) 25 | 26 | legendViews = UIView() 27 | addSubview(legendViews) 28 | } 29 | 30 | required init?(coder: NSCoder) { super.init(coder: coder) } 31 | 32 | public func update(data: [Data]) { 33 | for index in 0.. CGRect { 56 | let count: CGFloat = CGFloat(legendViews.subviews.count) 57 | let gap: CGFloat = 15, size = CGSize(width: 150, height: 50) 58 | let paddingTop = (bounds.height - count * size.height - (count - 1) * gap) / 2 59 | let originY = paddingTop + CGFloat(index) * (size.height + gap) 60 | let originX = (bounds.width - size.width) / 3 61 | return CGRect(x: originX, y: originY, width: size.width, height: size.height) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /OverlayController/view/OverlaySocialView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OverlaySocialView.swift 3 | // OverlayController 4 | // 5 | // Created by zhanghao on 2020/2/15. 6 | // Copyright © 2020 zhanghao. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @objc public protocol OverlaySocialViewDelegate { 12 | 13 | @objc optional func overlaySocialViewDidClickedClose(_ socialView: OverlaySocialView) 14 | @objc optional func overlaySocialView(_ socialView: OverlaySocialView, didSelectItemAt indexPath: IndexPath) 15 | } 16 | 17 | public class OverlaySocialView: UIView, UIScrollViewDelegate { 18 | 19 | public weak var delegate: OverlaySocialViewDelegate? 20 | 21 | public struct Data { 22 | var image: UIImage? 23 | var title: String? 24 | } 25 | 26 | public var dataList: [[Data]]! { 27 | didSet { 28 | guard !dataList.isEmpty else { return } 29 | update(data: dataList!) 30 | } 31 | } 32 | 33 | private var backButton: UIButton! 34 | private var scrollView: UIScrollView! 35 | private var sub1Views: UIView! 36 | private var sub2Views: UIView! 37 | private var separateView: OverlaySocialSeparateView! 38 | private var gradientView: UIView! 39 | private var _isOpened = true 40 | 41 | override init(frame: CGRect) { 42 | super.init(frame: frame) 43 | backgroundColor = UIColor.pk.rgba(red: 245, green: 245, blue: 245) 44 | 45 | scrollView = UIScrollView(frame: bounds) 46 | scrollView.delegate = self 47 | scrollView.showsVerticalScrollIndicator = false 48 | scrollView.alwaysBounceVertical = true 49 | if #available(iOS 11.0, *) { 50 | scrollView.contentInsetAdjustmentBehavior = .never 51 | } else { 52 | // Fallback on earlier versions 53 | } 54 | addSubview(scrollView) 55 | 56 | backButton = UIButton(type: .custom) 57 | backButton.titleLabel?.font = UIFont.pk.fontName(.pingFangSC, style: .light, size: 28) 58 | backButton.setTitle("×", for: .normal) 59 | backButton.setTitleColor(.black, for: .normal) 60 | backButton.addTarget(self, action: #selector(closed), for: .touchUpInside) 61 | addSubview(backButton) 62 | 63 | sub1Views = UIView() 64 | scrollView.addSubview(sub1Views) 65 | 66 | sub2Views = UIView() 67 | scrollView.addSubview(sub2Views) 68 | 69 | separateView = OverlaySocialSeparateView() 70 | separateView.button.addTarget(self, action: #selector(changed), for: .touchUpInside) 71 | scrollView.addSubview(separateView) 72 | 73 | gradientView = UIView() 74 | gradientView.isUserInteractionEnabled = false 75 | addSubview(gradientView) 76 | } 77 | 78 | required init?(coder: NSCoder) { super.init(coder: coder) } 79 | 80 | @objc private func closed() { 81 | delegate?.overlaySocialViewDidClickedClose?(self) 82 | } 83 | 84 | let offset: CGFloat = 50 85 | @objc private func changed() { 86 | _isOpened = !_isOpened 87 | if _isOpened { 88 | var oldFrame = sub2Views.frame 89 | oldFrame.origin.y = separateView.frame.maxY + 25 + offset 90 | self.sub2Views.frame = oldFrame 91 | self.sub2Views.alpha = 0 92 | UIView.animate(withDuration: 0.25, animations: { 93 | oldFrame.origin.y -= self.offset 94 | self.sub2Views.frame = oldFrame 95 | self.sub2Views.alpha = 1 96 | self.separateView.button.imageView?.transform = .identity 97 | self.separateView.button.setTitle("Fold", for: .normal) 98 | }) { (_) in 99 | self.scrollView.contentSize.height = self.sub2Views.frame.maxY 100 | } 101 | } else { 102 | var oldFrame = sub2Views.frame 103 | oldFrame.origin.y = separateView.frame.maxY + 25 104 | self.sub2Views.frame = oldFrame 105 | self.sub2Views.alpha = 1 106 | UIView.animate(withDuration: 0.25, animations: { 107 | oldFrame.origin.y += self.offset 108 | self.sub2Views.frame = oldFrame 109 | self.sub2Views.alpha = 0 110 | self.scrollView.contentOffset = .zero 111 | self.separateView.button.imageView?.transform = CGAffineTransform(rotationAngle: .pi) 112 | self.separateView.button.setTitle("More", for: .normal) 113 | }) { (_) in 114 | self.scrollView.contentSize.height = self.sub1Views.frame.maxY 115 | } 116 | } 117 | 118 | let animation = CATransition() 119 | animation.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut) 120 | animation.type = .fade 121 | animation.duration = 0.25 122 | self.separateView.button.titleLabel?.layer.add(animation, forKey: nil) 123 | } 124 | 125 | private func update(data: [[Data]]) { 126 | for index in 0.. 90 { 160 | delegate?.overlaySocialViewDidClickedClose?(self) 161 | } 162 | } 163 | 164 | public override func layoutSubviews() { 165 | super.layoutSubviews() 166 | backButton.frame = CGRect(x: bounds.width - 50, y: UIScreen.pk.safeInsets.top, width: 50, height: 60) 167 | gradientView.frame = CGRect(x: 0, y: bounds.height - 100, width: bounds.width, height: 100) 168 | gradientView.pk.addGradient(colors: [UIColor.white.withAlphaComponent(0), UIColor.white.withAlphaComponent(1)], direction: .topToBottom) 169 | 170 | guard !sub1Views.subviews.isEmpty else { return } 171 | 172 | for index in 0.. CGRect { 191 | let lineCount: CGFloat = 3 192 | let padding: CGFloat = 30, size: CGSize = CGSize(width: 72, height: 92) 193 | let allGap = bounds.width - (padding * 2) - (size.width * lineCount) 194 | let lineSpacing = floor(allGap / (lineCount - 1)), interSpacing: CGFloat = 10 195 | let v = CGFloat(Int(CGFloat(index) / lineCount)) 196 | let h = CGFloat(Int(CGFloat(index).truncatingRemainder(dividingBy: lineCount))) 197 | let originY = v * (size.height + interSpacing) 198 | let originX = h * (size.width + lineSpacing) + padding 199 | return CGRect.init(x: originX, y: originY, width: size.width, height: size.height) 200 | } 201 | } 202 | 203 | fileprivate class OverlaySocialViewCell: UIView { 204 | let image = UIButton(type: .custom) 205 | let message = UILabel() 206 | 207 | override init(frame: CGRect) { 208 | super.init(frame: frame) 209 | image.isUserInteractionEnabled = false 210 | image.clipsToBounds = false 211 | image.layer.masksToBounds = true 212 | addSubview(image) 213 | 214 | message.isUserInteractionEnabled = false 215 | message.textColor = .black 216 | message.font = UIFont.pk.fontName(.pingFangSC, style: .light, size: 14) 217 | message.textAlignment = .center 218 | message.numberOfLines = 1 219 | addSubview(message) 220 | } 221 | 222 | required init?(coder: NSCoder) { super.init(coder: coder) } 223 | 224 | func update(data: OverlaySocialView.Data?) { 225 | image.setImage(data?.image, for: .normal) 226 | message.text = data?.title 227 | } 228 | 229 | override func layoutSubviews() { 230 | super.layoutSubviews() 231 | let gap: CGFloat = 10 232 | let padding: CGFloat = 35, bw = bounds.width - padding 233 | image.frame = CGRect(x: padding / 2, y: 0, width: bw, height: bw) 234 | guard let text = message.text, text.count > 0 else { return } 235 | let limitH = bounds.height - image.frame.height - gap 236 | var size = message.sizeThatFits(CGSize(width: bounds.width, height: CGFloat.greatestFiniteMagnitude)) 237 | size.height = min(size.height, limitH) 238 | size.width = bounds.width 239 | message.frame = CGRect(origin: CGPoint(x: 0, y: bw + gap), size: size) 240 | } 241 | } 242 | 243 | fileprivate class OverlaySocialSeparateView: UICollectionReusableView { 244 | let button = PKLegendButton() 245 | let line1 = UIView() 246 | let line2 = UIView() 247 | 248 | override init(frame: CGRect) { 249 | super.init(frame: frame) 250 | button.backgroundColor = .white 251 | button.titleLabel?.font = UIFont.pk.fontName(.palatino, style: .italic, size: 14) 252 | button.setTitleColor(.black, for: .normal) 253 | button.setTitle("Fold", for: .normal) 254 | button.setImage(UIImage(named: "arrowup"), for: .normal) 255 | button._imageSize = CGSize(width: 17, height: 15) 256 | button._update(layoutType: .right, itemSpacing: 2) 257 | button.layer.masksToBounds = true 258 | button.isUserInteractionEnabled = true 259 | addSubview(button) 260 | 261 | line1.backgroundColor = UIColor.black.withAlphaComponent(0.1) 262 | line2.backgroundColor = UIColor.black.withAlphaComponent(0.1) 263 | addSubview(line1) 264 | addSubview(line2) 265 | } 266 | 267 | required init?(coder: NSCoder) { super.init(coder: coder) } 268 | 269 | override func layoutSubviews() { 270 | super.layoutSubviews() 271 | let bw: CGFloat = 90, bh: CGFloat = 24 272 | let padding: CGFloat = 25, gap: CGFloat = 18 273 | let lineW = (bounds.width - padding * 2 - gap * 2 - bw) * 0.5 274 | button.frame = CGRect(x: 0, y: 0, width: bw, height: bh) 275 | button.layer.cornerRadius = bh / 2 276 | line1.frame = CGRect(x: padding, y: 0, width: lineW, height: 1 / UIScreen.main.scale) 277 | line2.frame = CGRect(x: bounds.width - lineW - padding, y: 0, width: lineW, height: 1 / UIScreen.main.scale) 278 | button.center = CGPoint(x: bounds.width * 0.5, y: bounds.height * 0.5) 279 | line1.center.y = bounds.height * 0.5 280 | line2.center.y = bounds.height * 0.5 281 | } 282 | } 283 | -------------------------------------------------------------------------------- /OverlayController/view/OverlayTextView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OverlayTextView.swift 3 | // OverlayController 4 | // 5 | // Created by zhanghao on 2020/2/17. 6 | // Copyright © 2020 zhanghao. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | public class OverlayTextView: UIView { 12 | 13 | public var inputLimitLength: Int = 10 { 14 | didSet { textLimitUpdates() } 15 | } 16 | 17 | private var textView: PKTextView! 18 | private var limitLabel: UILabel! 19 | 20 | override init(frame: CGRect) { 21 | super.init(frame: frame) 22 | bindNotifications() 23 | 24 | textView = PKTextView() 25 | textView.font = UIFont.pk.fontName(.gillSans, size: 16) 26 | textView.textColor = .black 27 | textView.placeholder = "请输入" 28 | textView.placeholderColor = .lightGray 29 | addSubview(textView) 30 | 31 | limitLabel = UILabel() 32 | limitLabel.textColor = .lightGray 33 | limitLabel.textAlignment = .right 34 | limitLabel.font = UIFont.pk.fontName(.gillSans, style: .italic, size: 16) 35 | addSubview(limitLabel) 36 | 37 | textLimitUpdates() 38 | } 39 | 40 | public func becomeFirstResponder() { 41 | textView.becomeFirstResponder() 42 | } 43 | 44 | public func resignFirstResponder() { 45 | textView.resignFirstResponder() 46 | } 47 | 48 | required init?(coder: NSCoder) { super.init(coder: coder) } 49 | 50 | override public func layoutSubviews() { 51 | super.layoutSubviews() 52 | textView.frame = CGRect(x: 10, y: 20, width: bounds.width - 20, height: 60) 53 | limitLabel.frame = CGRect(x: 10, y: textView.frame.maxY, width: textView.bounds.width - 5, height: 25) 54 | } 55 | 56 | private func bindNotifications() { 57 | NotificationCenter.default.addObserver(self, selector: #selector(textDidChange(_:)), name: UITextView.textDidChangeNotification, object: nil) 58 | } 59 | 60 | private func unbindNotifications() { 61 | NotificationCenter.default.removeObserver(self, name: UITextView.textDidChangeNotification, object: nil) 62 | } 63 | 64 | @objc private func textDidChange(_ notif: Notification) { 65 | textLimitUpdates() 66 | } 67 | 68 | private func textLimitUpdates() { 69 | guard inputLimitLength > 0 else { return } 70 | if textView.text.count > inputLimitLength { 71 | limitLabel.textColor = .red 72 | } else { 73 | limitLabel.textColor = .lightGray 74 | } 75 | limitLabel.text = "\(textView.text.count)/\(inputLimitLength)" 76 | } 77 | 78 | deinit { 79 | unbindNotifications() 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /OverlayControllerTests/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 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | CFBundleDisplayName 22 | OverlayController 23 | 24 | 25 | -------------------------------------------------------------------------------- /OverlayControllerTests/OverlayControllerTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OverlayControllerTests.swift 3 | // OverlayControllerTests 4 | // 5 | // Created by ahong on 2020/2/26. 6 | // Copyright © 2020 zhanghao. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import OverlayController 11 | 12 | class OverlayControllerTests: XCTestCase { 13 | 14 | override func setUp() { 15 | // Put setup code here. This method is called before the invocation of each test method in the class. 16 | } 17 | 18 | override func tearDown() { 19 | // Put teardown code here. This method is called after the invocation of each test method in the class. 20 | } 21 | 22 | func testExample() { 23 | // This is an example of a functional test case. 24 | // Use XCTAssert and related functions to verify your tests produce the correct results. 25 | } 26 | 27 | func testPerformanceExample() { 28 | // This is an example of a performance test case. 29 | self.measure { 30 | // Put the code you want to measure the time of here. 31 | } 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /OverlayControllerUITests/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 | 22 | 23 | -------------------------------------------------------------------------------- /OverlayControllerUITests/OverlayControllerUITests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OverlayControllerUITests.swift 3 | // OverlayControllerUITests 4 | // 5 | // Created by ahong on 2020/2/26. 6 | // Copyright © 2020 zhanghao. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | class OverlayControllerUITests: XCTestCase { 12 | 13 | override func setUp() { 14 | // Put setup code here. This method is called before the invocation of each test method in the class. 15 | 16 | // In UI tests it is usually best to stop immediately when a failure occurs. 17 | continueAfterFailure = false 18 | 19 | // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. 20 | } 21 | 22 | override func tearDown() { 23 | // Put teardown code here. This method is called after the invocation of each test method in the class. 24 | } 25 | 26 | func testExample() { 27 | // UI tests must launch the application that they test. 28 | let app = XCUIApplication() 29 | app.launch() 30 | 31 | // Use recording to get started writing UI tests. 32 | // Use XCTAssert and related functions to verify your tests produce the correct results. 33 | } 34 | 35 | func testLaunchPerformance() { 36 | if #available(macOS 10.15, iOS 13.0, tvOS 13.0, *) { 37 | // This measures how long it takes to launch your application. 38 | measure(metrics: [XCTOSSignpostMetric.applicationLaunch]) { 39 | XCUIApplication().launch() 40 | } 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | [![Swift 5.0](https://img.shields.io/badge/Swift-5.0-orange.svg?style=flat)](https://developer.apple.com/swift/) 7 | ![enter image description here](https://img.shields.io/badge/pod-v1.0.0-brightgreen.svg) 8 | ![enter image description here](https://img.shields.io/badge/platform-iOS%2010.0%2B-ff69b5152950834.svg) 9 | 10 | 11 | OverlayController is an implementation of a overlay effect for any view. It can be used to easily add dynamics to user interactions and popups views. 12 | 13 | . 14 | 15 | 16 | 17 | ## Example 18 | 19 | To run the example project, clone the repo, and run `pod install` from the Example directory first. 20 | 21 | ## Requirements 22 | 23 | * Swift 5.0 24 | * iOS 10 or higher 25 | 26 | ## Installation 27 | 28 | OverlayController is available through [CocoaPods](https://cocoapods.org). To install 29 | it, simply add the following line to your Podfile: 30 | 31 | ```ruby 32 | pod 'OverlayController', '~> 1.0.0' 33 | ``` 34 | 35 | ## Usage 36 | 37 | Todo... 38 | 39 | ## Author 40 | 41 | 40460770@qq.com, 40460770@qq.com 42 | 43 | ## License 44 | 45 | OverlayController is available under the MIT license. See the LICENSE file for more info. 46 | -------------------------------------------------------------------------------- /imgs/1585744742654.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snail-z/zhLoadingView/ac3112b830a2ebff786aa27426f8b6950379b64b/imgs/1585744742654.jpg -------------------------------------------------------------------------------- /imgs/zhpopupController05.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snail-z/zhLoadingView/ac3112b830a2ebff786aa27426f8b6950379b64b/imgs/zhpopupController05.gif -------------------------------------------------------------------------------- /imgs/zhpopupController11.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snail-z/zhLoadingView/ac3112b830a2ebff786aa27426f8b6950379b64b/imgs/zhpopupController11.gif --------------------------------------------------------------------------------