├── README.md
├── ExtensionKit.xcodeproj
├── project.xcworkspace
│ └── contents.xcworkspacedata
└── xcshareddata
│ └── xcschemes
│ └── ExtensionKit.xcscheme
├── Sources
├── Objc
│ ├── ObjcSwizzle.h
│ └── ObjcSwizzle.m
├── Info.plist
├── Extension
│ ├── Timer+Extension.swift
│ ├── UITabBar+Extension.swift
│ ├── DispatchQueue+Extension.swift
│ ├── UITableView+Extension.swift
│ ├── Bundle+Extension.swift
│ ├── UIScreen+Extension.swift
│ ├── NSError+Extension.swift
│ ├── Data+Extension.swift
│ ├── UINavigationBar+Extension.swift
│ ├── UILabel+Extension.swift
│ ├── UITextView+Extension.swift
│ ├── UIDevice+Extension.swift
│ ├── UIResponder+Extension.swift
│ ├── UserDefaults+Extension.swift
│ ├── NSObject+Extension.swift
│ ├── UITextField+Extension.swift
│ ├── Dictionary+Extension.swift
│ ├── UIBarButtonItem+Extension.swift
│ ├── UIApplication+Extension.swift
│ ├── Array+Extension.swift
│ ├── Number+Extension.swift
│ ├── CALayer+Extension.swift
│ ├── UIColor+Extension.swift
│ ├── UIFont+Extension.swift
│ ├── UIScrollView+Extension.swift
│ ├── UIControl+Extension.swift
│ ├── Lang+Extension.swift
│ ├── UIButton+Extension.swift
│ ├── UIViewController+Extension.swift
│ ├── String+Extension.swift
│ ├── UIImage+Extension.swift
│ └── CoreGraphics+Extension.swift
├── Classes
│ ├── NoPasteTextField.swift
│ ├── Observable.swift
│ ├── DashlineView.swift
│ ├── CustomHeightNavigationBar.swift
│ ├── TextObserver.swift
│ ├── PlaceholderTextView.swift
│ ├── CGD.swift
│ └── BadgeView.swift
├── ExtensionKit.h
└── Protocols
│ └── ExtensionProtocols.swift
├── .gitignore
├── LICENSE
└── ExtensionKit
└── Sample
└── ViewController.swift
/README.md:
--------------------------------------------------------------------------------
1 | # ExtensionKit
2 | The extension utils code for swift on app development.
3 |
4 | The vendors extension split to [VendorsExtension](https://github.com/cuzv/VendorsExtension)
5 |
--------------------------------------------------------------------------------
/ExtensionKit.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Sources/Objc/ObjcSwizzle.h:
--------------------------------------------------------------------------------
1 | //
2 | // ObjcSwizzle.h
3 | // ExtensionKit
4 | //
5 | // Created by Moch Xiao on 3/31/17.
6 | // Copyright © 2017 Moch. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | @interface UIView (ObjcSwizzle)
12 | @end
13 |
14 | @interface UILabel (ObjcSwizzle)
15 | @end
16 |
17 | @interface UITextField (ObjcSwizzle)
18 | @end
19 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Xcode
2 | #
3 | build/
4 | *.pbxuser
5 | !default.pbxuser
6 | *.mode1v3
7 | !default.mode1v3
8 | *.mode2v3
9 | !default.mode2v3
10 | *.perspectivev3
11 | !default.perspectivev3
12 | xcuserdata
13 | *.xccheckout
14 | *.moved-aside
15 | DerivedData
16 | *.hmap
17 | *.ipa
18 | *.xcuserstate
19 |
20 | # CocoaPods
21 | #
22 | # We recommend against adding the Pods directory to your .gitignore. However
23 | # you should judge for yourself, the pros and cons are mentioned at:
24 | # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control
25 | #
26 | Pods/
27 | .DS_Store
28 | .svn/
29 | xcuserdata/
30 |
--------------------------------------------------------------------------------
/Sources/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | $(CURRENT_PROJECT_VERSION)
23 | NSPrincipalClass
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 snownothing
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Sources/Extension/Timer+Extension.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Timer+Extension.swift
3 | // ExtensionKit
4 | //
5 | // Created by Haioo Inc on 4/7/17.
6 | // Copyright © 2017 Moch. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public extension Timer {
12 | public func resume(after duration: TimeInterval = 0) {
13 | fireDate = Date(timeIntervalSinceNow: duration)
14 | }
15 |
16 | public func pause(after duration: TimeInterval = 0) {
17 | if 0 <= duration {
18 | fireDate = Date.distantFuture
19 | } else {
20 | DispatchQueue.main.asyncAfter(deadline: .now() + duration) {
21 | self.fireDate = Date.distantFuture
22 | }
23 | }
24 | }
25 | }
26 |
27 | public extension CADisplayLink {
28 | public func resume(after duration: TimeInterval = 0) {
29 | if 0 <= duration {
30 | isPaused = false
31 | } else {
32 | DispatchQueue.main.asyncAfter(deadline: .now() + duration) {
33 | self.isPaused = false
34 | }
35 | }
36 | }
37 |
38 | public func pause(after duration: TimeInterval = 0) {
39 | if 0 <= duration {
40 | isPaused = true
41 | } else {
42 | DispatchQueue.main.asyncAfter(deadline: .now() + duration) {
43 | self.isPaused = true
44 | }
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/Sources/Classes/NoPasteTextField.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NoPasteTextField.swift
3 | // Copyright (c) 2015-2016 Red Rain (http://mochxiao.com).
4 | //
5 | // Permission is hereby granted, free of charge, to any person obtaining a copy
6 | // of this software and associated documentation files (the "Software"), to deal
7 | // in the Software without restriction, including without limitation the rights
8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | // copies of the Software, and to permit persons to whom the Software is
10 | // furnished to do so, subject to the following conditions:
11 | //
12 | // The above copyright notice and this permission notice shall be included in
13 | // all copies or substantial portions of the Software.
14 | //
15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | // THE SOFTWARE.
22 | //
23 |
24 | import UIKit
25 |
26 | final public class NoPasteTextField: UITextField {
27 | public override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
28 | UIMenuController.shared.isMenuVisible = false
29 | return false
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/Sources/Extension/UITabBar+Extension.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UITabBar+Extension.swift
3 | // Copyright (c) 2015-2016 Red Rain (http://mochxiao.com).
4 | //
5 | // Permission is hereby granted, free of charge, to any person obtaining a copy
6 | // of this software and associated documentation files (the "Software"), to deal
7 | // in the Software without restriction, including without limitation the rights
8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | // copies of the Software, and to permit persons to whom the Software is
10 | // furnished to do so, subject to the following conditions:
11 | //
12 | // The above copyright notice and this permission notice shall be included in
13 | // all copies or substantial portions of the Software.
14 | //
15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | // THE SOFTWARE.
22 | //
23 |
24 | import UIKit
25 |
26 | public extension UITabBar {
27 | public var shimShadowView: UIView? {
28 | if let
29 | _backgroundView = value(forKey: "_backgroundView") as? NSObject,
30 | let _shadowView = _backgroundView.value(forKey: "_shadowView") as? UIView
31 | {
32 | return _shadowView
33 | }
34 | return nil
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/Sources/ExtensionKit.h:
--------------------------------------------------------------------------------
1 | //
2 | // ExtensionKit.h
3 | // Copyright (c) 2015-2016 Red Rain (http://mochxiao.com).
4 | //
5 | // Permission is hereby granted, free of charge, to any person obtaining a copy
6 | // of this software and associated documentation files (the "Software"), to deal
7 | // in the Software without restriction, including without limitation the rights
8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | // copies of the Software, and to permit persons to whom the Software is
10 | // furnished to do so, subject to the following conditions:
11 | //
12 | // The above copyright notice and this permission notice shall be included in
13 | // all copies or substantial portions of the Software.
14 | //
15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | // THE SOFTWARE.
22 | //
23 |
24 | #import
25 |
26 | //! Project version number for ExtensionKit.
27 | FOUNDATION_EXPORT double ExtensionKitVersionNumber;
28 |
29 | //! Project version string for ExtensionKit.
30 | FOUNDATION_EXPORT const unsigned char ExtensionKitVersionString[];
31 |
32 | // In this header, you should import all the public headers of your framework using statements like #import
33 |
34 | #import
35 |
36 |
--------------------------------------------------------------------------------
/Sources/Classes/Observable.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Observable.swift
3 | // Copyright (c) 2015-2016 Red Rain (http://mochxiao.com).
4 | //
5 | // Permission is hereby granted, free of charge, to any person obtaining a copy
6 | // of this software and associated documentation files (the "Software"), to deal
7 | // in the Software without restriction, including without limitation the rights
8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | // copies of the Software, and to permit persons to whom the Software is
10 | // furnished to do so, subject to the following conditions:
11 | //
12 | // The above copyright notice and this permission notice shall be included in
13 | // all copies or substantial portions of the Software.
14 | //
15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | // THE SOFTWARE.
22 | //
23 |
24 | import Foundation
25 |
26 | open class Observable {
27 | public typealias Observer = (T) -> ()
28 | fileprivate var observer: Observer?
29 | fileprivate var value: T {
30 | didSet {
31 | observer?(value)
32 | }
33 | }
34 |
35 | init(_ v: T) {
36 | value = v
37 | }
38 |
39 | open func observe(_ observer: Observer?) {
40 | self.observer = observer
41 | observer?(value)
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/Sources/Extension/DispatchQueue+Extension.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DispatchQueue+Extension.swift
3 | // ExtensionKit
4 | //
5 | // Copyright (c) 2015-2016 Red Rain (http://mochxiao.com).
6 | //
7 | // Permission is hereby granted, free of charge, to any person obtaining a copy
8 | // of this software and associated documentation files (the "Software"), to deal
9 | // in the Software without restriction, including without limitation the rights
10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | // copies of the Software, and to permit persons to whom the Software is
12 | // furnished to do so, subject to the following conditions:
13 | //
14 | // The above copyright notice and this permission notice shall be included in
15 | // all copies or substantial portions of the Software.
16 | //
17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 | // THE SOFTWARE.
24 | //
25 |
26 | import Foundation
27 |
28 | public extension DispatchQueue {
29 | private static var _onceTracker = [String]()
30 |
31 | public class func once(token: String, block: ()-> ()) {
32 | objc_sync_enter(self)
33 | defer { objc_sync_exit(self) }
34 |
35 | if _onceTracker.contains(token) {
36 | return
37 | }
38 |
39 | _onceTracker.append(token)
40 |
41 | block()
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/Sources/Extension/UITableView+Extension.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UITableView+Extension.swift
3 | // Copyright (c) 2015-2016 Red Rain (http://mochxiao.com).
4 | //
5 | // Permission is hereby granted, free of charge, to any person obtaining a copy
6 | // of this software and associated documentation files (the "Software"), to deal
7 | // in the Software without restriction, including without limitation the rights
8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | // copies of the Software, and to permit persons to whom the Software is
10 | // furnished to do so, subject to the following conditions:
11 | //
12 | // The above copyright notice and this permission notice shall be included in
13 | // all copies or substantial portions of the Software.
14 | //
15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | // THE SOFTWARE.
22 | //
23 |
24 | import UIKit
25 |
26 | public extension UITableView {
27 | public func scrollToTop(animated: Bool = true) {
28 | if let indexPath = indexPathForRow(at: CGPoint(x: bounds.midX, y: 2)) , !isEmpty {
29 | scrollToRow(at: indexPath, at: .top, animated: animated)
30 | }
31 | }
32 |
33 | public var isEmpty: Bool {
34 | var count = 0
35 | for section in 0 ..< numberOfSections {
36 | count += numberOfRows(inSection: section)
37 | }
38 | return count == 0
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/Sources/Extension/Bundle+Extension.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Bundle+Extension.swift
3 | // Copyright (c) 2015-2016 Red Rain (http://mochxiao.com).
4 | //
5 | // Permission is hereby granted, free of charge, to any person obtaining a copy
6 | // of this software and associated documentation files (the "Software"), to deal
7 | // in the Software without restriction, including without limitation the rights
8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | // copies of the Software, and to permit persons to whom the Software is
10 | // furnished to do so, subject to the following conditions:
11 | //
12 | // The above copyright notice and this permission notice shall be included in
13 | // all copies or substantial portions of the Software.
14 | //
15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | // THE SOFTWARE.
22 | //
23 |
24 | import Foundation
25 |
26 | public extension Bundle {
27 | fileprivate static let _mainBundle = Bundle.main
28 |
29 | public class var build: String {
30 | return _mainBundle.infoDictionary?["CFBundleVersion"] as? String ?? ""
31 | }
32 |
33 | public class var ver: String {
34 | return _mainBundle.infoDictionary?["CFBundleShortVersionString"] as? String ?? ""
35 | }
36 |
37 | public class var displayName: String {
38 | return _mainBundle.infoDictionary?["CFBundleDisplayName"] as? String ?? ""
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/Sources/Extension/UIScreen+Extension.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIScreen+Extension.swift
3 | // Copyright (c) 2015-2016 Red Rain (http://mochxiao.com).
4 | //
5 | // Permission is hereby granted, free of charge, to any person obtaining a copy
6 | // of this software and associated documentation files (the "Software"), to deal
7 | // in the Software without restriction, including without limitation the rights
8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | // copies of the Software, and to permit persons to whom the Software is
10 | // furnished to do so, subject to the following conditions:
11 | //
12 | // The above copyright notice and this permission notice shall be included in
13 | // all copies or substantial portions of the Software.
14 | //
15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | // THE SOFTWARE.
22 | //
23 |
24 | import UIKit
25 |
26 | public extension UIScreen {
27 | static let _width = UIScreen.main.bounds.size.width
28 | public class var width: CGFloat {
29 | return _width
30 | }
31 |
32 | static let _height = UIScreen.main.bounds.size.height
33 | public class var height: CGFloat {
34 | return _height
35 | }
36 |
37 | static let _scale = UIScreen.main.scale
38 | public class var scaleValue: CGFloat {
39 | return _scale
40 | }
41 |
42 | static let _size = UIScreen.main.bounds.size
43 | public class var size: CGSize {
44 | return _size
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/Sources/Extension/NSError+Extension.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NSError+Extension.swift
3 | // Copyright (c) 2015-2016 Red Rain (http://mochxiao.com).
4 | //
5 | // Permission is hereby granted, free of charge, to any person obtaining a copy
6 | // of this software and associated documentation files (the "Software"), to deal
7 | // in the Software without restriction, including without limitation the rights
8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | // copies of the Software, and to permit persons to whom the Software is
10 | // furnished to do so, subject to the following conditions:
11 | //
12 | // The above copyright notice and this permission notice shall be included in
13 | // all copies or substantial portions of the Software.
14 | //
15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | // THE SOFTWARE.
22 | //
23 |
24 | import Foundation
25 |
26 | public extension NSError {
27 | public class var emptyErrorDomain: String { return "com.mochxiao.error.default" }
28 | public class var emptyErrorCode: Int { return 1024 }
29 | public class var empty: NSError {
30 | return NSError(domain: emptyErrorDomain, code: emptyErrorCode, userInfo: nil)
31 | }
32 |
33 | public class func make(message: String, code: Int = 9999) -> NSError {
34 | return NSError(
35 | domain: "com.mochxiao.error.maker",
36 | code: code,
37 | userInfo: [NSLocalizedDescriptionKey: message, NSLocalizedFailureReasonErrorKey: message]
38 | )
39 | }
40 | }
41 |
42 | public func NSErrorFrom(message: String, code: Int = 9999) -> NSError {
43 | return NSError.make(message: message, code: code)
44 | }
45 |
--------------------------------------------------------------------------------
/ExtensionKit/Sample/ViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewController.swift
3 | // Copyright (c) 2015-2016 Moch Xiao (http://mochxiao.com).
4 | //
5 | // Permission is hereby granted, free of charge, to any person obtaining a copy
6 | // of this software and associated documentation files (the "Software"), to deal
7 | // in the Software without restriction, including without limitation the rights
8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | // copies of the Software, and to permit persons to whom the Software is
10 | // furnished to do so, subject to the following conditions:
11 | //
12 | // The above copyright notice and this permission notice shall be included in
13 | // all copies or substantial portions of the Software.
14 | //
15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | // THE SOFTWARE.
22 | //
23 |
24 | import UIKit
25 |
26 | class ViewController: UIViewController {
27 |
28 | override func viewDidLoad() {
29 | super.viewDidLoad()
30 | // Do any additional setup after loading the view, typically from a nib.
31 |
32 | let value = "value"
33 | UserDefaults["some"] = value
34 |
35 | }
36 |
37 | override func didReceiveMemoryWarning() {
38 | super.didReceiveMemoryWarning()
39 | // Dispose of any resources that can be recreated.
40 | }
41 |
42 |
43 | }
44 |
45 |
46 | enum Color {
47 | case Red
48 | case Green
49 |
50 | var rawValue: UIColor {
51 | switch self {
52 | case .Red: return UIColor.redColor()
53 | case .Green: return UIColor.greenColor()
54 | }
55 | }
56 | }
57 |
58 | let color = Color.Red.rawValue
59 |
60 |
--------------------------------------------------------------------------------
/Sources/Extension/Data+Extension.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Data+Extension.swift
3 | // Copyright (c) 2015-2016 Red Rain (http://mochxiao.com).
4 | //
5 | // Permission is hereby granted, free of charge, to any person obtaining a copy
6 | // of this software and associated documentation files (the "Software"), to deal
7 | // in the Software without restriction, including without limitation the rights
8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | // copies of the Software, and to permit persons to whom the Software is
10 | // furnished to do so, subject to the following conditions:
11 | //
12 | // The above copyright notice and this permission notice shall be included in
13 | // all copies or substantial portions of the Software.
14 | //
15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | // THE SOFTWARE.
22 | //
23 |
24 | import Foundation
25 |
26 | public extension Data {
27 | /// Create a Foundation object from JSON data.
28 | public var JSONObject: AnyObject? {
29 | do {
30 | return try JSONSerialization.jsonObject(with: self, options: []) as AnyObject
31 | } catch let error as NSError {
32 | logging("Deserialized JSON string failed with error: \(error)")
33 | return nil
34 | }
35 | }
36 |
37 | /// Generate JSON data from a Foundation object
38 | public static func make(fromJSONObject obj: AnyObject) -> Data? {
39 | do {
40 | return try JSONSerialization.data(withJSONObject: obj, options: [])
41 | } catch let error as NSError {
42 | logging("Serialized JSON string failed with error: \(error)")
43 | return nil
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/Sources/Extension/UINavigationBar+Extension.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UINavigationBar+Extension.swift
3 | // Copyright (c) 2015-2016 Red Rain (http://mochxiao.com).
4 | //
5 | // Permission is hereby granted, free of charge, to any person obtaining a copy
6 | // of this software and associated documentation files (the "Software"), to deal
7 | // in the Software without restriction, including without limitation the rights
8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | // copies of the Software, and to permit persons to whom the Software is
10 | // furnished to do so, subject to the following conditions:
11 | //
12 | // The above copyright notice and this permission notice shall be included in
13 | // all copies or substantial portions of the Software.
14 | //
15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | // THE SOFTWARE.
22 | //
23 |
24 | import UIKit
25 |
26 | // MARK: - Visible & Invisible
27 |
28 | public extension UINavigationBar {
29 | /// Only effect when `translucent` is true.
30 | public func setBackgroundVisible(_ visible: Bool) {
31 | if !isTranslucent {
32 | logging("`translucent` must be true if you wanna change background visible.")
33 | }
34 |
35 | setBackgroundImage(visible ? nil : UIImage(), for: .default)
36 | shadowImage = visible ? nil : UIImage()
37 | }
38 | }
39 |
40 | // MARK: - Hairline
41 |
42 | public extension UINavigationBar {
43 | public var shimShadowView: UIView? {
44 | if let
45 | _backgroundView = value(forKey: "_backgroundView") as? NSObject,
46 | let _shadowView = _backgroundView.value(forKey: "_shadowView") as? UIView
47 | {
48 | return _shadowView
49 | }
50 | return nil
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/Sources/Objc/ObjcSwizzle.m:
--------------------------------------------------------------------------------
1 | //
2 | // ObjcSwizzle.m
3 | // ExtensionKit
4 | //
5 | // Created by Moch Xiao on 3/31/17.
6 | // Copyright © 2017 Moch. All rights reserved.
7 | //
8 |
9 | #import "ObjcSwizzle.h"
10 | #import
11 |
12 | void EKSwizzleInstanceMethod(Class _Nonnull clazz, SEL _Nonnull originalSelector, SEL _Nonnull overrideSelector) {
13 | Method originalMethod = class_getInstanceMethod(clazz, originalSelector);
14 | Method overrideMethod = class_getInstanceMethod(clazz, overrideSelector);
15 |
16 | if (class_addMethod(clazz, originalSelector, method_getImplementation(overrideMethod), method_getTypeEncoding(overrideMethod))) {
17 | class_replaceMethod(clazz, overrideSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
18 | } else {
19 | method_exchangeImplementations(originalMethod, overrideMethod);
20 | }
21 | }
22 |
23 | #pragma mark -
24 |
25 | @implementation UIView (ObjcSwizzle)
26 |
27 | + (void)load {
28 | static dispatch_once_t onceToken;
29 | dispatch_once(&onceToken, ^{
30 | if (self.class == UIView.class) {
31 | EKSwizzleInstanceMethod(self.class, @selector(pointInside:withEvent:), @selector(_ek_pointInside:with:));
32 | }
33 | });
34 | }
35 |
36 | @end
37 |
38 | #pragma mark -
39 |
40 | @implementation UILabel (ObjcSwizzle)
41 |
42 | + (void)load {
43 | static dispatch_once_t onceToken;
44 | dispatch_once(&onceToken, ^{
45 | if (self.class == UILabel.class) {
46 | EKSwizzleInstanceMethod(self.class, @selector(intrinsicContentSize), @selector(_ek_intrinsicContentSize));
47 | EKSwizzleInstanceMethod(self.class, @selector(drawInRect:), @selector(_ek_drawTextIn:));
48 | }
49 | });
50 | }
51 |
52 | @end
53 |
54 | #pragma mark -
55 |
56 | @implementation UITextField (ObjcSwizzle)
57 |
58 | + (void)load {
59 | static dispatch_once_t onceToken;
60 | dispatch_once(&onceToken, ^{
61 | if (self.class == UITextField.class) {
62 | EKSwizzleInstanceMethod(self.class, @selector(intrinsicContentSize), @selector(_ek_intrinsicContentSize));
63 | EKSwizzleInstanceMethod(self.class, @selector(textRectForBounds:), @selector(_ek_textRectForBounds:));
64 | EKSwizzleInstanceMethod(self.class, @selector(editingRectForBounds:), @selector(_ek_editingRectForBounds:));
65 | }
66 | });
67 | }
68 |
69 | @end
70 |
--------------------------------------------------------------------------------
/Sources/Extension/UILabel+Extension.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UILabel+Extension.swift
3 | // Copyright (c) 2015-2016 Red Rain (http://mochxiao.com).
4 | //
5 | // Permission is hereby granted, free of charge, to any person obtaining a copy
6 | // of this software and associated documentation files (the "Software"), to deal
7 | // in the Software without restriction, including without limitation the rights
8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | // copies of the Software, and to permit persons to whom the Software is
10 | // furnished to do so, subject to the following conditions:
11 | //
12 | // The above copyright notice and this permission notice shall be included in
13 | // all copies or substantial portions of the Software.
14 | //
15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | // THE SOFTWARE.
22 | //
23 |
24 | import UIKit
25 |
26 | private struct AssociationKey {
27 | fileprivate static var contentInsets: String = "com.mochxiao.uilabel.contentInsets"
28 | }
29 |
30 | public extension UILabel {
31 | public var contentInsets: UIEdgeInsets {
32 | get {
33 | if let value = associatedObject(forKey: &AssociationKey.contentInsets) as? NSValue {
34 | return value.uiEdgeInsetsValue
35 | }
36 | return UIEdgeInsets.zero
37 | }
38 | set { associate(retainObject: NSValue(uiEdgeInsets: newValue), forKey: &AssociationKey.contentInsets) }
39 | }
40 |
41 | var _ek_intrinsicContentSize: CGSize {
42 | // MARK: 4 fucking Xcode8/iOS10 SDKs
43 | setNeedsLayout()
44 | layoutIfNeeded()
45 |
46 | let size = sizeThatFits(CGSize(width: bounds.size.width, height: bounds.size.height))
47 | let width = size.width + contentInsets.left + contentInsets.right
48 | let height = size.height + contentInsets.top + contentInsets.bottom
49 | return CGSize(width: width, height: height)
50 | }
51 |
52 | func _ek_drawText(in rect: CGRect) {
53 | _ek_drawText(in: UIEdgeInsetsInsetRect(rect, contentInsets))
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/Sources/Extension/UITextView+Extension.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UITextView+Extension.swift
3 | // Copyright (c) 2015-2016 Red Rain (http://mochxiao.com).
4 | //
5 | // Permission is hereby granted, free of charge, to any person obtaining a copy
6 | // of this software and associated documentation files (the "Software"), to deal
7 | // in the Software without restriction, including without limitation the rights
8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | // copies of the Software, and to permit persons to whom the Software is
10 | // furnished to do so, subject to the following conditions:
11 | //
12 | // The above copyright notice and this permission notice shall be included in
13 | // all copies or substantial portions of the Software.
14 | //
15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | // THE SOFTWARE.
22 | //
23 |
24 | import UIKit
25 |
26 | // MARK: - AssociationKey
27 |
28 | private struct AssociationKey {
29 | fileprivate static var textViewTextObserver: String = "com.mochxiao.uitextview.textViewTextObserver"
30 | }
31 |
32 | // MARK: - TextObserver Extension
33 |
34 | private extension UITextView {
35 | var textViewTextObserver: TextObserver {
36 | get { return associatedObject(forKey: &AssociationKey.textViewTextObserver) as! TextObserver }
37 | set { associate(retainObject: newValue, forKey: &AssociationKey.textViewTextObserver) }
38 | }
39 | }
40 |
41 | public extension UITextView {
42 | public func setupTextObserver(maxLength: Int = 100, actionHandler: ((Int) -> ())? = nil) {
43 | let textObserver = TextObserver(maxLength: maxLength) { (remainCount) -> () in
44 | actionHandler?(remainCount)
45 | }
46 | textObserver.observe(textView: self)
47 | textViewTextObserver = textObserver
48 | }
49 | }
50 |
51 | // MARK: -
52 |
53 | public extension UITextView {
54 | public func scrollCursorToVisible() {
55 | let cursorLocation = selectedRange.location
56 | let range = NSMakeRange(0, cursorLocation)
57 | scrollRangeToVisible(range)
58 | }
59 | }
60 |
61 |
62 |
--------------------------------------------------------------------------------
/Sources/Extension/UIDevice+Extension.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIDevice+Extension.swift
3 | // Copyright (c) 2015-2016 Red Rain (http://mochxiao.com).
4 | //
5 | // Permission is hereby granted, free of charge, to any person obtaining a copy
6 | // of this software and associated documentation files (the "Software"), to deal
7 | // in the Software without restriction, including without limitation the rights
8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | // copies of the Software, and to permit persons to whom the Software is
10 | // furnished to do so, subject to the following conditions:
11 | //
12 | // The above copyright notice and this permission notice shall be included in
13 | // all copies or substantial portions of the Software.
14 | //
15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | // THE SOFTWARE.
22 | //
23 |
24 | import UIKit
25 |
26 | public extension UIDevice {
27 | fileprivate static let _currentDevice = UIDevice.current
28 |
29 | public class var sysVersion: String {
30 | return _currentDevice.systemVersion
31 | }
32 |
33 | public class var majorVersion: Int {
34 | return Int(UIDevice.sysVersion.components(separatedBy: ".").first!)!
35 | }
36 |
37 | public class var iOS7x: Bool {
38 | return majorVersion >= 7
39 | }
40 |
41 | public class var iOS8x: Bool {
42 | return majorVersion >= 8
43 | }
44 |
45 | public class var iOS9x: Bool {
46 | return majorVersion >= 9
47 | }
48 |
49 | public class var iOS10x: Bool {
50 | return majorVersion >= 10
51 | }
52 |
53 | public class var iOS11x: Bool {
54 | return majorVersion >= 11
55 | }
56 |
57 | fileprivate static func deviceOrientation(_ result: (UIDeviceOrientation) -> ()) {
58 | if !_currentDevice.isGeneratingDeviceOrientationNotifications {
59 | _currentDevice.beginGeneratingDeviceOrientationNotifications()
60 | }
61 | result(_currentDevice.orientation)
62 | _currentDevice.endGeneratingDeviceOrientationNotifications()
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/Sources/Extension/UIResponder+Extension.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIResponder+Extension.swift
3 | // Copyright (c) 2015-2016 Red Rain (http://mochxiao.com).
4 | //
5 | // Permission is hereby granted, free of charge, to any person obtaining a copy
6 | // of this software and associated documentation files (the "Software"), to deal
7 | // in the Software without restriction, including without limitation the rights
8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | // copies of the Software, and to permit persons to whom the Software is
10 | // furnished to do so, subject to the following conditions:
11 | //
12 | // The above copyright notice and this permission notice shall be included in
13 | // all copies or substantial portions of the Software.
14 | //
15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | // THE SOFTWARE.
22 | //
23 |
24 | import UIKit
25 |
26 | public extension UIResponder {
27 | public func responder(ofClass cls: AnyClass) -> UIResponder? {
28 | var responder = self
29 | while let _responder = responder.next {
30 | if _responder.isKind(of: cls) {
31 | return _responder
32 | }
33 |
34 | responder = _responder
35 | }
36 | return nil
37 | }
38 |
39 | @discardableResult
40 | public func sendAction(_ action: Selector) -> Bool {
41 | return UIApplication.sendAction(action, fromSender: self)
42 | }
43 |
44 | @discardableResult
45 | public func performAction(
46 | _ action: Selector,
47 | _ firstArgument: AnyObject! = nil,
48 | _ secondArgument: AnyObject! = nil) -> Unmanaged!
49 | {
50 | var responder: UIResponder? = self
51 | while let _responder = responder , !_responder.responds(to: action) {
52 | responder = _responder.next
53 | }
54 |
55 | if nil == firstArgument {
56 | return responder?.perform(action)
57 | } else if nil == secondArgument {
58 | return responder?.perform(action, with: firstArgument)
59 | } else {
60 | return responder?.perform(action, with: firstArgument, with: secondArgument)
61 | }
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/Sources/Protocols/ExtensionProtocols.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ExtensionProtocols.swift
3 | // Copyright (c) 2015-2016 Red Rain (http://mochxiao.com).
4 | //
5 | // Permission is hereby granted, free of charge, to any person obtaining a copy
6 | // of this software and associated documentation files (the "Software"), to deal
7 | // in the Software without restriction, including without limitation the rights
8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | // copies of the Software, and to permit persons to whom the Software is
10 | // furnished to do so, subject to the following conditions:
11 | //
12 | // The above copyright notice and this permission notice shall be included in
13 | // all copies or substantial portions of the Software.
14 | //
15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | // THE SOFTWARE.
22 | //
23 |
24 | import Foundation
25 |
26 | // See: http://ericasadun.com/2016/04/18/default-reflection/
27 | /// A reference type that provides a better default representation than the class name.
28 | public protocol DefaultReflectable: CustomStringConvertible {}
29 |
30 | /// A default implementation that enables class members to display their values.
31 | extension DefaultReflectable {
32 | /// Constructs a better representation using reflection.
33 | internal func defaultDescription(_ instance: T) -> String {
34 | let mirror = Mirror(reflecting: instance)
35 | let chunks = mirror.children.map { (label: String?, value: Any) -> String in
36 | if let label = label {
37 | if value is String {
38 | return "\(label): \"\(value)\""
39 | }
40 | return "\(label): \(value)"
41 | } else {
42 | return "\(value)"
43 | }
44 | }
45 | if chunks.count > 0 {
46 | let chunksString = chunks.joined(separator: ", ")
47 | return "\(mirror.subjectType)(\(chunksString))"
48 | } else {
49 | return "\(instance)"
50 | }
51 | }
52 |
53 | /// Conforms to CustomStringConvertible.
54 | public var description: String {
55 | return defaultDescription(self)
56 | }
57 | }
58 | //
59 | //extension NSObject: DefaultReflectable {
60 | //}
61 |
62 |
--------------------------------------------------------------------------------
/Sources/Classes/DashlineView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DashlineView.swift
3 | // Copyright (c) 2015-2016 Red Rain (http://mochxiao.com).
4 | //
5 | // Permission is hereby granted, free of charge, to any person obtaining a copy
6 | // of this software and associated documentation files (the "Software"), to deal
7 | // in the Software without restriction, including without limitation the rights
8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | // copies of the Software, and to permit persons to whom the Software is
10 | // furnished to do so, subject to the following conditions:
11 | //
12 | // The above copyright notice and this permission notice shall be included in
13 | // all copies or substantial portions of the Software.
14 | //
15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | // THE SOFTWARE.
22 | //
23 |
24 | import UIKit
25 |
26 | final public class DashlineView: UIView {
27 | public var spacing: CGFloat = 2
28 | public var lineColor: UIColor = UIColor.separator
29 | public var horizontal: Bool = true
30 |
31 | public override func draw(_ rect: CGRect) {
32 | super.draw(rect)
33 |
34 | backgroundColor?.setFill()
35 | UIRectFill(rect)
36 |
37 | let lineWidth = horizontal ? rect.height.ceilling : rect.width.ceilling
38 | let startPoint = horizontal ? CGPoint(x: (lineWidth / 2).ceilling, y: (rect.height / 2).ceilling) :
39 | CGPoint(x: (rect.width / 2).ceilling , y: (lineWidth / 2).ceilling)
40 | let endPoint = horizontal ? CGPoint(x: rect.width - (lineWidth / 2).ceilling, y: (rect.height / 2).ceilling) :
41 | CGPoint(x: (rect.width / 2).ceilling , y: rect.height - (lineWidth / 2).ceilling)
42 |
43 | guard let context = UIGraphicsGetCurrentContext() else {
44 | return
45 | }
46 | context.beginPath()
47 | context.setLineWidth(lineWidth)
48 | context.setStrokeColor(lineColor.cgColor)
49 | context.setLineDash(phase: 0, lengths: [spacing, spacing])
50 | context.move(to: CGPoint(x: startPoint.x, y: startPoint.y))
51 | context.addLine(to: CGPoint(x: endPoint.x, y: endPoint.y))
52 | context.strokePath()
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/Sources/Extension/UserDefaults+Extension.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UserDefaults+Extension.swift
3 | // Copyright (c) 2015-2016 Red Rain (http://mochxiao.com).
4 | //
5 | // Permission is hereby granted, free of charge, to any person obtaining a copy
6 | // of this software and associated documentation files (the "Software"), to deal
7 | // in the Software without restriction, including without limitation the rights
8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | // copies of the Software, and to permit persons to whom the Software is
10 | // furnished to do so, subject to the following conditions:
11 | //
12 | // The above copyright notice and this permission notice shall be included in
13 | // all copies or substantial portions of the Software.
14 | //
15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | // THE SOFTWARE.
22 | //
23 |
24 | import UIKit
25 |
26 | public extension Foundation.UserDefaults {
27 | public subscript(key: String) -> Any? {
28 | get { return value(forKey: key) as Any }
29 | set {
30 | switch newValue {
31 | case let value as Int: set(value, forKey: key)
32 | case let value as Double: set(value, forKey: key)
33 | case let value as Bool: set(value, forKey: key)
34 | case let value as URL: set(value, forKey: key)
35 | case let value as NSObject: set(value, forKey: key)
36 | case nil: removeObject(forKey: key)
37 | default: assertionFailure("Invalid value type.")
38 | }
39 | }
40 | }
41 |
42 | fileprivate func setter(key: String, value: Any?) {
43 | self[key] = value
44 | synchronize()
45 | }
46 |
47 | /// Is there a object for specific key exist.
48 | public func hasKey(_ key: String) -> Bool {
49 | return nil != object(forKey: key)
50 | }
51 |
52 | /// Archive object to NSData to save.
53 | public func archive(object: Any?, forKey key: String) {
54 | if let value = object {
55 | setter(key: key, value: NSKeyedArchiver.archivedData(withRootObject: value) as Any?)
56 | } else {
57 | removeObject(forKey: key)
58 | }
59 | }
60 |
61 | /// Unarchive object for specific key.
62 | public func unarchivedObject(forKey key: String) -> Any? {
63 | return data(forKey: key).flatMap { NSKeyedUnarchiver.unarchiveObject(with: $0) as Any }
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/Sources/Classes/CustomHeightNavigationBar.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CustomHeightNavigationBar.swift
3 | // Copyright (c) 2015-2016 Red Rain (http://mochxiao.com).
4 | //
5 | // Permission is hereby granted, free of charge, to any person obtaining a copy
6 | // of this software and associated documentation files (the "Software"), to deal
7 | // in the Software without restriction, including without limitation the rights
8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | // copies of the Software, and to permit persons to whom the Software is
10 | // furnished to do so, subject to the following conditions:
11 | //
12 | // The above copyright notice and this permission notice shall be included in
13 | // all copies or substantial portions of the Software.
14 | //
15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | // THE SOFTWARE.
22 | //
23 |
24 | import UIKit
25 |
26 | // See: http://www.emdentec.com/blog/2014/2/25/hacking-uinavigationbar
27 | open class CustomHeightNavigationBar: UINavigationBar {
28 | fileprivate let defaultHeight: CGFloat = 44
29 | @IBInspectable
30 | open var customHeight: CGFloat = 88
31 |
32 | public override init(frame: CGRect) {
33 | super.init(frame: frame)
34 | commitInit()
35 | }
36 |
37 | public required init?(coder aDecoder: NSCoder) {
38 | super.init(coder: aDecoder)
39 | commitInit()
40 | }
41 |
42 | open override func awakeFromNib() {
43 | super.awakeFromNib()
44 | commitInit()
45 | }
46 |
47 | fileprivate func commitInit() {
48 | transform = CGAffineTransform.identity
49 | transform = CGAffineTransform(translationX: 0, y: defaultHeight - customHeight)
50 | }
51 |
52 | open override func sizeThatFits(_ size: CGSize) -> CGSize {
53 | var newSize = super.sizeThatFits(size)
54 | newSize.height = customHeight
55 | return newSize
56 | }
57 |
58 | open override func layoutSubviews() {
59 | super.layoutSubviews()
60 |
61 | let classNamesToReposition = ["_UINavigationBarBackground"]
62 | for view in subviews {
63 | if classNamesToReposition.contains(NSStringFromClass(type(of: view))) {
64 | var newFrame = view.frame
65 | let statusBarHeight = UIApplication.shared.statusBarFrame.size.height
66 | newFrame.origin.y = bounds.origin.y + customHeight - defaultHeight - statusBarHeight
67 | newFrame.size.height = bounds.size.height + statusBarHeight
68 | view.frame = newFrame
69 | }
70 | }
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/Sources/Extension/NSObject+Extension.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NSObject+Extension.swift
3 | // Copyright (c) 2015-2016 Red Rain (http://mochxiao.com).
4 | //
5 | // Permission is hereby granted, free of charge, to any person obtaining a copy
6 | // of this software and associated documentation files (the "Software"), to deal
7 | // in the Software without restriction, including without limitation the rights
8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | // copies of the Software, and to permit persons to whom the Software is
10 | // furnished to do so, subject to the following conditions:
11 | //
12 | // The above copyright notice and this permission notice shall be included in
13 | // all copies or substantial portions of the Software.
14 | //
15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | // THE SOFTWARE.
22 | //
23 |
24 | import Foundation
25 |
26 | // MARK: - Associate Object
27 |
28 | public extension NSObject {
29 | /// Sets an associated value for a given object using a weak reference to the associated object.
30 | /// **Note**: the `key` underlying type must be String.
31 | public func associate(assignObject object: Any?, forKey key: UnsafeRawPointer) {
32 | let strKey: String = convertUnsafePointerToSwiftType(key)
33 | willChangeValue(forKey: strKey)
34 | objc_setAssociatedObject(self, key, object, .OBJC_ASSOCIATION_ASSIGN)
35 | didChangeValue(forKey: strKey)
36 | }
37 |
38 | /// Sets an associated value for a given object using a strong reference to the associated object.
39 | /// **Note**: the `key` underlying type must be String.
40 | public func associate(retainObject object: Any?, forKey key: UnsafeRawPointer) {
41 | let strKey: String = convertUnsafePointerToSwiftType(key)
42 | willChangeValue(forKey: strKey)
43 | objc_setAssociatedObject(self, key, object, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
44 | didChangeValue(forKey: strKey)
45 | }
46 |
47 | /// Sets an associated value for a given object using a copied reference to the associated object.
48 | /// **Note**: the `key` underlying type must be String.
49 | public func associate(copyObject object: Any?, forKey key: UnsafeRawPointer) {
50 | let strKey: String = convertUnsafePointerToSwiftType(key)
51 | willChangeValue(forKey: strKey)
52 | objc_setAssociatedObject(self, key, object, .OBJC_ASSOCIATION_COPY_NONATOMIC)
53 | didChangeValue(forKey: strKey)
54 | }
55 |
56 | /// Returns the value associated with a given object for a given key.
57 | /// **Note**: the `key` underlying type must be String.
58 | public func associatedObject(forKey key: UnsafeRawPointer) -> Any? {
59 | return objc_getAssociatedObject(self, key)
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/ExtensionKit.xcodeproj/xcshareddata/xcschemes/ExtensionKit.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
31 |
32 |
33 |
34 |
35 |
36 |
47 |
48 |
54 |
55 |
56 |
57 |
58 |
59 |
65 |
66 |
72 |
73 |
74 |
75 |
77 |
78 |
81 |
82 |
83 |
--------------------------------------------------------------------------------
/Sources/Extension/UITextField+Extension.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UITextField+Extension.swift
3 | // Copyright (c) 2015-2016 Red Rain (http://mochxiao.com).
4 | //
5 | // Permission is hereby granted, free of charge, to any person obtaining a copy
6 | // of this software and associated documentation files (the "Software"), to deal
7 | // in the Software without restriction, including without limitation the rights
8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | // copies of the Software, and to permit persons to whom the Software is
10 | // furnished to do so, subject to the following conditions:
11 | //
12 | // The above copyright notice and this permission notice shall be included in
13 | // all copies or substantial portions of the Software.
14 | //
15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | // THE SOFTWARE.
22 | //
23 |
24 | import UIKit
25 |
26 | // MARK: - AssociationKey
27 |
28 | private struct AssociationKey {
29 | fileprivate static var textFieldTextObserver: String = "com.mochxiao.uitextfield.textFieldTextObserver"
30 | fileprivate static var contentInsets: String = "com.mochxiao.uitextfield.contentInsets"
31 | }
32 |
33 | // MARK: -
34 |
35 | public extension UITextField {
36 | public var contentInsets: UIEdgeInsets {
37 | get {
38 | if let value = associatedObject(forKey: &AssociationKey.contentInsets) as? NSValue {
39 | return value.uiEdgeInsetsValue
40 | }
41 | return UIEdgeInsets.zero
42 | }
43 | set { associate(retainObject: NSValue(uiEdgeInsets: newValue), forKey: &AssociationKey.contentInsets) }
44 | }
45 |
46 | var _ek_intrinsicContentSize: CGSize {
47 | // MARK: 4 fucking Xcode8/iOS10 SDKs
48 | setNeedsLayout()
49 | layoutIfNeeded()
50 |
51 | let size = sizeThatFits(CGSize(width: bounds.size.width, height: bounds.size.height))
52 | let width = size.width + contentInsets.left + contentInsets.right
53 | let height = size.height + contentInsets.top + contentInsets.bottom
54 | return CGSize(width: width, height: height)
55 | }
56 |
57 | func _ek_textRect(forBounds bounds: CGRect) -> CGRect {
58 | return UIEdgeInsetsInsetRect(bounds, contentInsets)
59 | }
60 |
61 | func _ek_editingRect(forBounds bounds: CGRect) -> CGRect {
62 | return UIEdgeInsetsInsetRect(bounds, contentInsets)
63 | }
64 | }
65 |
66 | // MARK: - TextObserver Extension
67 |
68 | private extension UITextField {
69 | var textFieldTextObserver: TextObserver {
70 | get { return associatedObject(forKey: &AssociationKey.textFieldTextObserver) as! TextObserver }
71 | set { associate(retainObject: newValue, forKey: &AssociationKey.textFieldTextObserver) }
72 | }
73 | }
74 |
75 | public extension UITextField {
76 | public func setupTextObserver(maxLength: Int = 100, actionHandler: ((Int) -> ())? = nil) {
77 | let textObserver = TextObserver(maxLength: maxLength) { (remainCount) -> () in
78 | actionHandler?(remainCount)
79 | }
80 | textObserver.observe(textField: self)
81 | textFieldTextObserver = textObserver
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/Sources/Extension/Dictionary+Extension.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Dictionary+Extension.swift
3 | // Copyright (c) 2015-2016 Red Rain (http://mochxiao.com).
4 | //
5 | // Permission is hereby granted, free of charge, to any person obtaining a copy
6 | // of this software and associated documentation files (the "Software"), to deal
7 | // in the Software without restriction, including without limitation the rights
8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | // copies of the Software, and to permit persons to whom the Software is
10 | // furnished to do so, subject to the following conditions:
11 | //
12 | // The above copyright notice and this permission notice shall be included in
13 | // all copies or substantial portions of the Software.
14 | //
15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | // THE SOFTWARE.
22 | //
23 |
24 | import Foundation
25 |
26 | // MARK: - Dictionary
27 |
28 | public extension Dictionary {
29 | private func queryComponents(fromKey key: String, value: Any) -> [(String, String)] {
30 | var components: [(String, String)] = []
31 |
32 | if let dictionary = value as? [String: Any] {
33 | for (nestedKey, value) in dictionary {
34 | components += queryComponents(fromKey: "\(key)[\(nestedKey)]", value: value)
35 | }
36 | } else if let array = value as? [Any] {
37 | for value in array {
38 | components += queryComponents(fromKey: "\(key)[]", value: value)
39 | }
40 | } else if let value = value as? NSNumber {
41 | if value.isBool {
42 | components.append((key.escaped, (value.boolValue ? "1" : "0").escaped))
43 | } else {
44 | components.append((key.escaped, "\(value)".escaped))
45 | }
46 | } else if let bool = value as? Bool {
47 | components.append((key.escaped, (bool ? "1" : "0").escaped))
48 | } else {
49 | components.append((key.escaped, "\(value)".escaped))
50 | }
51 |
52 | return components
53 | }
54 |
55 | /// Stolen from Alamofire
56 | private func query(_ parameters: [String: Any]) -> String {
57 | var components: [(String, String)] = []
58 |
59 | for key in parameters.keys.sorted(by: <) {
60 | let value = parameters[key]!
61 | components += queryComponents(fromKey: key, value: value)
62 | }
63 |
64 | return components.map { "\($0)=\($1)" }.joined(separator: "&")
65 | }
66 |
67 | public var queryString: String {
68 | var newDic: [String: Any] = [:]
69 | for element in self {
70 | newDic["\(element.key)"] = element.value
71 | }
72 | return query(newDic)
73 | }
74 |
75 | public var JSONString: String? {
76 | if let data = Data.make(fromJSONObject: self as AnyObject) {
77 | return String(data: data, encoding: String.Encoding.utf8)
78 | }
79 | return nil
80 | }
81 | }
82 |
83 | /// Combine two `Dictionary` to one.
84 | public func += (
85 | lhs: inout Dictionary,
86 | rhs: Dictionary)
87 | {
88 | for (key, value) in rhs {
89 | lhs.updateValue(value, forKey: key)
90 | }
91 | }
92 |
93 |
--------------------------------------------------------------------------------
/Sources/Extension/UIBarButtonItem+Extension.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIBarButtonItem+Extension.swift
3 | // Copyright (c) 2015-2016 Red Rain (http://mochxiao.com).
4 | //
5 | // Permission is hereby granted, free of charge, to any person obtaining a copy
6 | // of this software and associated documentation files (the "Software"), to deal
7 | // in the Software without restriction, including without limitation the rights
8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | // copies of the Software, and to permit persons to whom the Software is
10 | // furnished to do so, subject to the following conditions:
11 | //
12 | // The above copyright notice and this permission notice shall be included in
13 | // all copies or substantial portions of the Software.
14 | //
15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | // THE SOFTWARE.
22 | //
23 |
24 | import UIKit
25 |
26 | // MARK: - AssociationKey
27 |
28 | private struct AssociationKey {
29 | fileprivate static var barButtonItemActionHandlerWrapper: String = "com.mochxiao.uibarbuttonitem.barButtonItemActionHandlerWrapper"
30 | }
31 |
32 | // MARK: -
33 |
34 | public extension UIBarButtonItem {
35 | public class func make(fixedSpace width: CGFloat) -> UIBarButtonItem {
36 | let spacing = UIBarButtonItem(barButtonSystemItem: .fixedSpace, target: nil, action: nil)
37 | spacing.width = width
38 | return spacing
39 | }
40 | }
41 |
42 | // MARK: -
43 |
44 | public extension UIBarButtonItem {
45 | fileprivate var barButtonItemActionHandlerWrapper: ClosureDecorator? {
46 | get { return associatedObject(forKey: &AssociationKey.barButtonItemActionHandlerWrapper) as? ClosureDecorator }
47 | set { associate(retainObject: newValue, forKey: &AssociationKey.barButtonItemActionHandlerWrapper) }
48 | }
49 |
50 | public class func make(title: String, actionHandler: ((UIBarButtonItem) -> ())?) -> UIBarButtonItem {
51 | let barButtonItem = UIBarButtonItem(title: title, style: .plain, target: self, action: #selector(UIBarButtonItem.performActionHandler(sender:)))
52 | if let actionHandler = actionHandler {
53 | barButtonItem.barButtonItemActionHandlerWrapper = ClosureDecorator(actionHandler)
54 | }
55 | return barButtonItem
56 | }
57 |
58 | public class func make(image: UIImage?, actionHandler: ((UIBarButtonItem) -> ())?) -> UIBarButtonItem {
59 | let barButtonItem = UIBarButtonItem(image: image?.original, style: .plain, target: self, action: #selector(UIBarButtonItem.performActionHandler(sender:)))
60 | if let actionHandler = actionHandler {
61 | barButtonItem.barButtonItemActionHandlerWrapper = ClosureDecorator(actionHandler)
62 | }
63 | return barButtonItem
64 | }
65 |
66 | public class func make(systemItem item: UIBarButtonSystemItem, actionHandler: ((UIBarButtonItem) -> ())?) -> UIBarButtonItem {
67 | let barButtonItem = UIBarButtonItem(barButtonSystemItem: item, target: self, action: #selector(UIBarButtonItem.performActionHandler(sender:)))
68 | if let actionHandler = actionHandler {
69 | barButtonItem.barButtonItemActionHandlerWrapper = ClosureDecorator(actionHandler)
70 | }
71 | return barButtonItem
72 | }
73 |
74 | /// Helper func
75 | @objc internal class func performActionHandler(sender: UIBarButtonItem) {
76 | sender.barButtonItemActionHandlerWrapper?.invoke(sender)
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/Sources/Extension/UIApplication+Extension.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIApplication+Extension.swift
3 | // Copyright (c) 2015-2016 Red Rain (http://mochxiao.com).
4 | //
5 | // Permission is hereby granted, free of charge, to any person obtaining a copy
6 | // of this software and associated documentation files (the "Software"), to deal
7 | // in the Software without restriction, including without limitation the rights
8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | // copies of the Software, and to permit persons to whom the Software is
10 | // furnished to do so, subject to the following conditions:
11 | //
12 | // The above copyright notice and this permission notice shall be included in
13 | // all copies or substantial portions of the Software.
14 | //
15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | // THE SOFTWARE.
22 | //
23 |
24 | import UIKit
25 |
26 | // MARK: - Actions
27 |
28 | public extension UIApplication {
29 | fileprivate static let _sharedApplication = UIApplication.shared
30 |
31 | public class func open(url: Foundation.URL) {
32 | if _sharedApplication.canOpenURL(url) {
33 | _sharedApplication.openURL(url)
34 | } else {
35 | logging("Can not execute the given action.")
36 | }
37 | }
38 |
39 | public class func open(urlPath: String) {
40 | if let url = URL(string: urlPath) {
41 | UIApplication.open(url: url)
42 | }
43 | }
44 |
45 | public class func makePhone(to phoneNumber: String) {
46 | open(urlPath: "telprompt:\(phoneNumber)")
47 | }
48 |
49 | public class func sendMessage(to phoneNumber: String) {
50 | open(urlPath: "sms:\(phoneNumber)")
51 | }
52 |
53 | public class func email(to email: String) {
54 | open(urlPath: "mailto:\(email)")
55 | }
56 |
57 | public class func chatQQ(to qq: String) {
58 | open(urlPath: "mqq://im/chat?chat_type=wpa&uin=\(qq)&version=1&src_type=iOS")
59 | }
60 |
61 | public class func clearIconBadge() {
62 | let badgeNumber = _sharedApplication.applicationIconBadgeNumber
63 | _sharedApplication.applicationIconBadgeNumber = 1
64 | _sharedApplication.applicationIconBadgeNumber = 0
65 | _sharedApplication.cancelAllLocalNotifications()
66 | _sharedApplication.applicationIconBadgeNumber = badgeNumber
67 | }
68 |
69 | public class func sendAction(_ action: Selector, fromSender sender: AnyObject?, forEvent event: UIEvent? = nil) -> Bool {
70 | // Get the target in the responder chain
71 | var target = sender
72 |
73 | while let _target = target , !_target.canPerformAction(action, withSender: sender) {
74 | target = _target.next
75 | }
76 |
77 | if let _target = target {
78 | return UIApplication.shared.sendAction(action, to: _target, from: sender, for: event)
79 | }
80 |
81 | return false
82 | }
83 |
84 | /// Setting the statusBarStyle does nothing if your application is using the default UIViewController-based status bar system.
85 | public class func makeStatusBarDark() {
86 | UIApplication.shared.statusBarStyle = .default
87 | }
88 |
89 | /// Setting the statusBarStyle does nothing if your application is using the default UIViewController-based status bar system.
90 | public class func makeStatusBarLight() {
91 | UIApplication.shared.statusBarStyle = .lightContent
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/Sources/Classes/TextObserver.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TextObserver.swift
3 | // Copyright (c) 2015-2016 Red Rain (http://mochxiao.com).
4 | //
5 | // Permission is hereby granted, free of charge, to any person obtaining a copy
6 | // of this software and associated documentation files (the "Software"), to deal
7 | // in the Software without restriction, including without limitation the rights
8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | // copies of the Software, and to permit persons to whom the Software is
10 | // furnished to do so, subject to the following conditions:
11 | //
12 | // The above copyright notice and this permission notice shall be included in
13 | // all copies or substantial portions of the Software.
14 | //
15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | // THE SOFTWARE.
22 | //
23 |
24 | import UIKit
25 |
26 | // MARK: - TextObserver
27 |
28 | final public class TextObserver {
29 | fileprivate let maxLength: Int
30 | fileprivate let actionHandler: ((Int) -> ())
31 | fileprivate var textFieldObserver: NSObjectProtocol?
32 | fileprivate var textViewObserver: NSObjectProtocol?
33 |
34 | public init(maxLength: Int, actionHandler: @escaping ((Int) -> ())) {
35 | self.maxLength = maxLength
36 | self.actionHandler = actionHandler
37 | }
38 |
39 | deinit {
40 | if let textFieldObserver = textFieldObserver {
41 | NotificationCenter.default.removeObserver(textFieldObserver)
42 | }
43 |
44 | if let textViewObserver = textViewObserver {
45 | NotificationCenter.default.removeObserver(textViewObserver)
46 | }
47 | }
48 |
49 | // MARK: - UITextField
50 |
51 | public func observe(textField object: UITextField) {
52 | textFieldObserver = NotificationCenter.default.addObserver(
53 | forName: NSNotification.Name.UITextFieldTextDidChange,
54 | object: object,
55 | queue: OperationQueue.main) { [weak self] (notification) -> Void in
56 | guard let this = self else { return }
57 | guard let textField = notification.object as? UITextField else { return }
58 | guard let text = textField.text else { return }
59 |
60 | let textLenght = text.count
61 | if textLenght > this.maxLength && nil == textField.markedTextRange {
62 | textField.text = text.substring(toIndex: this.maxLength)
63 | }
64 |
65 | this.actionHandler(this.maxLength - (textField.text ?? "").count)
66 | }
67 | }
68 |
69 | // MARK: - UITextView
70 |
71 | public func observe(textView object: UITextView) {
72 | textViewObserver = NotificationCenter.default.addObserver(
73 | forName: NSNotification.Name.UITextViewTextDidChange,
74 | object: object,
75 | queue: OperationQueue.main) { [weak self] (notification) -> Void in
76 | guard let this = self else { return }
77 | guard let textView = notification.object as? UITextView else { return }
78 | guard let text = textView.text else { return }
79 |
80 | let textLenght = text.count
81 | if textLenght > this.maxLength && nil == textView.markedTextRange {
82 | textView.text = text.substring(toIndex: this.maxLength)
83 | }
84 |
85 | this.actionHandler(this.maxLength - (textView.text ?? "").count)
86 | textView.scrollCursorToVisible()
87 | }
88 | }
89 |
90 | }
91 |
--------------------------------------------------------------------------------
/Sources/Extension/Array+Extension.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Array+Extension.swift
3 | // Copyright (c) 2015-2016 Red Rain (http://mochxiao.com).
4 | //
5 | // Permission is hereby granted, free of charge, to any person obtaining a copy
6 | // of this software and associated documentation files (the "Software"), to deal
7 | // in the Software without restriction, including without limitation the rights
8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | // copies of the Software, and to permit persons to whom the Software is
10 | // furnished to do so, subject to the following conditions:
11 | //
12 | // The above copyright notice and this permission notice shall be included in
13 | // all copies or substantial portions of the Software.
14 | //
15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | // THE SOFTWARE.
22 | //
23 |
24 | import Foundation
25 |
26 | // MARK: - Array
27 |
28 | public extension Array {
29 | public mutating func exchange(lhs index: Int, rhs otherIndex: Int) {
30 | if count <= index || count <= otherIndex {
31 | fatalError("Index beyond boundary.")
32 | }
33 | if index >= otherIndex {
34 | fatalError("lhs must less than rhs.")
35 | }
36 |
37 | let firstItemData = self[index]
38 | let firstRange = Range(index ..< index + 1)
39 |
40 | let secondaryItemData = self[otherIndex]
41 | let secondaryRange = Range(otherIndex ..< otherIndex + 1)
42 |
43 | replaceSubrange(firstRange, with: [secondaryItemData])
44 | replaceSubrange(secondaryRange, with: [firstItemData])
45 | }
46 |
47 | public mutating func replace(at index: Int, with element: Element) {
48 | if count <= index {
49 | fatalError("Index beyond boundary.")
50 | }
51 | let range = Range(index ..< index + 1)
52 | replaceSubrange(range, with: [element])
53 | }
54 |
55 | public mutating func replaceLast(_ element: Element) {
56 | replace(at: count - 1, with: element)
57 | }
58 |
59 | public mutating func replaceFirst(_ element: Element) {
60 | replace(at: 0, with: element)
61 | }
62 |
63 | public var prettyDebugDescription: String {
64 | var output: [String] = []
65 | var index = 0
66 | for item in self {
67 | output.append("\(index): \(item)")
68 | index += 1
69 | }
70 | return output.joined(separator: "\n")
71 | }
72 |
73 | public var second: Element? {
74 | if count > 1 { return self[1] }
75 | return nil
76 | }
77 |
78 | public var third: Element? {
79 | if count > 2 { return self[2] }
80 | return nil
81 | }
82 |
83 | public var fourth: Element? {
84 | if count > 3 { return self[3] }
85 | return nil
86 | }
87 |
88 | public var fifthly: Element? {
89 | if count > 4 { return self[4] }
90 | return nil
91 | }
92 |
93 | public var sixth: Element? {
94 | if count > 5 { return self[5] }
95 | return nil
96 | }
97 |
98 | public var seventh: Element? {
99 | if count > 6 { return self[6] }
100 | return nil
101 | }
102 |
103 | public var eighth: Element? {
104 | if count > 7 { return self[7] }
105 | return nil
106 | }
107 |
108 | public var ninth: Element? {
109 | if count > 8 { return self[8] }
110 | return nil
111 | }
112 |
113 | public var tenth: Element? {
114 | if count > 9 { return self[9] }
115 | return nil
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/Sources/Extension/Number+Extension.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Number+Extension.swift
3 | // Copyright (c) 2015-2016 Red Rain (http://mochxiao.com).
4 | //
5 | // Permission is hereby granted, free of charge, to any person obtaining a copy
6 | // of this software and associated documentation files (the "Software"), to deal
7 | // in the Software without restriction, including without limitation the rights
8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | // copies of the Software, and to permit persons to whom the Software is
10 | // furnished to do so, subject to the following conditions:
11 | //
12 | // The above copyright notice and this permission notice shall be included in
13 | // all copies or substantial portions of the Software.
14 | //
15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | // THE SOFTWARE.
22 | //
23 |
24 | import Foundation
25 |
26 | public extension NSNumber {
27 | public var isBool: Bool { return CFBooleanGetTypeID() == CFGetTypeID(self) }
28 | }
29 |
30 | // MARK: - Double -> String
31 |
32 | public extension Double {
33 | public var price: String {
34 | let str = String(format: "%.2f", self)
35 | if str.hasSuffix("00") {
36 | return String(format: "%.0f", self)
37 | }
38 | if str.hasSuffix("0") {
39 | return String(format: "%.1f", self)
40 | }
41 | return str
42 | }
43 | public var CNY: String {
44 | return "¥" + price
45 | }
46 |
47 | public var USD: String {
48 | return "$" + price
49 | }
50 |
51 | public var string: String {
52 | return String(self)
53 | }
54 |
55 | public var cgfloat: CGFloat {
56 | return CGFloat(self)
57 | }
58 | }
59 |
60 | // MARK: - Time & Date
61 |
62 | public extension Double {
63 | var formattedDate: String {
64 | let formatter = DateFormatter()
65 | formatter.dateFormat = "yyyy-MM-dd"
66 | let date = Date(timeIntervalSince1970: self)
67 | return formatter.string(from: date)
68 | }
69 |
70 | var formattedTime: String {
71 | let formatter = DateFormatter()
72 | formatter.dateFormat = "HH:mm"
73 | let date = Date(timeIntervalSince1970: self)
74 | return formatter.string(from: date)
75 | }
76 |
77 | var formattedDateTime: String {
78 | let formatter = DateFormatter()
79 | formatter.dateFormat = "yyyy-MM-dd HH:mm"
80 | let date = Date(timeIntervalSince1970: self)
81 | return formatter.string(from: date)
82 | }
83 | }
84 |
85 | public extension Int {
86 | var formattedDate: String {
87 | return Double(self).formattedDate
88 | }
89 |
90 | var formattedTime: String {
91 | return Double(self).formattedTime
92 | }
93 |
94 | var formattedDateTime: String {
95 | return Double(self).formattedDateTime
96 | }
97 |
98 | public var cgfloat: CGFloat {
99 | return CGFloat(self)
100 | }
101 | }
102 |
103 | public extension UInt {
104 | public var cgfloat: CGFloat {
105 | return CGFloat(self)
106 | }
107 | }
108 |
109 | public extension UInt8 {
110 | public var cgfloat: CGFloat {
111 | return CGFloat(self)
112 | }
113 | }
114 |
115 | public extension UInt16 {
116 | public var cgfloat: CGFloat {
117 | return CGFloat(self)
118 | }
119 | }
120 |
121 | public extension UInt32 {
122 | public var cgfloat: CGFloat {
123 | return CGFloat(self)
124 | }
125 | }
126 |
127 | public extension UInt64 {
128 | public var cgfloat: CGFloat {
129 | return CGFloat(self)
130 | }
131 | }
132 |
--------------------------------------------------------------------------------
/Sources/Classes/PlaceholderTextView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PlaceholderTextView.swift
3 | // Copyright (c) 2015-2016 Red Rain (http://mochxiao.com).
4 | //
5 | // Permission is hereby granted, free of charge, to any person obtaining a copy
6 | // of this software and associated documentation files (the "Software"), to deal
7 | // in the Software without restriction, including without limitation the rights
8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | // copies of the Software, and to permit persons to whom the Software is
10 | // furnished to do so, subject to the following conditions:
11 | //
12 | // The above copyright notice and this permission notice shall be included in
13 | // all copies or substantial portions of the Software.
14 | //
15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | // THE SOFTWARE.
22 | //
23 |
24 | import UIKit
25 |
26 | /// The UITextView subclass supported a placeholder property like UITextField.
27 | /// Optional has a counting remain text length present label.
28 | /// **Note**: Do not forget invoke `invokeTextObserver(maxLength:, actionHandler:)`.
29 | final public class PlaceholderTextView: UITextView {
30 | fileprivate let placeholderLabel: UILabel = {
31 | let label = UILabel()
32 | label.backgroundColor = UIColor.clear
33 | label.textColor = UIColor.placeholder
34 | label.font = UIFont.preferredFont(forTextStyle: UIFontTextStyle.footnote)
35 | label.lineBreakMode = .byTruncatingTail
36 | label.numberOfLines = 0
37 | return label
38 | }()
39 |
40 | public var placeholder: String? {
41 | didSet {
42 | placeholderLabel.text = placeholder
43 | updatePlaceholderLabelFrame()
44 | }
45 | }
46 |
47 | override public var font: UIFont! {
48 | didSet {
49 | placeholderLabel.font = font
50 | updatePlaceholderLabelFrame()
51 | }
52 | }
53 |
54 | override public var text: String? {
55 | didSet {
56 | if let text = text , text.count > 0 {
57 | placeholderLabel.isHidden = true
58 | } else {
59 | placeholderLabel.isHidden = false
60 | }
61 | }
62 | }
63 |
64 | public override init(frame: CGRect, textContainer: NSTextContainer?) {
65 | super.init(frame: frame, textContainer: textContainer)
66 | setup()
67 | }
68 |
69 | required public init?(coder aDecoder: NSCoder) {
70 | super.init(coder: aDecoder)
71 | setup()
72 | }
73 |
74 | public override func layoutSubviews() {
75 | super.layoutSubviews()
76 | updatePlaceholderLabelFrame()
77 | }
78 | }
79 |
80 | public extension PlaceholderTextView {
81 | fileprivate func setup() {
82 | addSubview(placeholderLabel)
83 | }
84 |
85 | fileprivate func updatePlaceholderLabelFrame() {
86 | if let size = placeholderLabel.text?.layoutSize(
87 | font: placeholderLabel.font,
88 | preferredMaxLayoutWidth: bounds.width - 10)
89 | {
90 | placeholderLabel.frame = CGRect(
91 | x: 5,
92 | y: 4,
93 | width: size.width + 10,
94 | height: size.height + 8
95 | )
96 | }
97 | }
98 |
99 | /// **Note**: Do not invoke `setupTextObserver(maxLength:, actionHandler:)` the both.
100 | public func invokeTextObserver(maxLength: Int = 100, actionHandler: ((Int) -> ())? = nil) {
101 | setupTextObserver(maxLength: maxLength) { [weak self] (remainCount) -> () in
102 | self?.placeholderLabel.isHidden = remainCount != maxLength
103 | actionHandler?(remainCount)
104 | }
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/Sources/Extension/CALayer+Extension.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CALayer+Extension.swift
3 | // ExtensionKit
4 | //
5 | // Created by Moch Xiao on 2/22/17.
6 | // Copyright © 2017 Moch. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import QuartzCore
11 |
12 | // MARK: - Frame & Struct
13 | public extension CALayer {
14 | public var origin: CGPoint {
15 | get { return frame.origin }
16 | set { frame = CGRect(x: newValue.x, y: newValue.y, width: width, height: height) }
17 | }
18 |
19 | public var size: CGSize {
20 | get { return frame.size }
21 | set { frame = CGRect(x: minX, y: minY, width: newValue.width, height: newValue.height) }
22 | }
23 |
24 | public var minX: CGFloat {
25 | get { return frame.origin.x }
26 | set { frame = CGRect(x: newValue, y: minY, width: width, height: height) }
27 | }
28 |
29 | public var left: CGFloat {
30 | get { return frame.origin.x }
31 | set { frame = CGRect(x: newValue, y: minY, width: width, height: height) }
32 | }
33 |
34 | public var midX: CGFloat {
35 | get { return frame.midX }
36 | set { frame = CGRect(x: newValue - width * 0.5, y: minY, width: width, height: height) }
37 | }
38 |
39 | public var centerX: CGFloat {
40 | get { return frame.midX }
41 | set { frame = CGRect(x: newValue - width * 0.5, y: minY, width: width, height: height) }
42 | }
43 |
44 | public var maxX: CGFloat {
45 | get { return minX + width }
46 | set { frame = CGRect(x: newValue - width, y: minY, width: width, height: height) }
47 | }
48 |
49 | public var right: CGFloat {
50 | get { return minX + width }
51 | set { frame = CGRect(x: newValue - width, y: minY, width: width, height: height) }
52 | }
53 |
54 | public var minY: CGFloat {
55 | get { return frame.origin.y }
56 | set { frame = CGRect(x: minX, y: newValue, width: width, height: height) }
57 | }
58 |
59 | public var top: CGFloat {
60 | get { return frame.origin.y }
61 | set { frame = CGRect(x: minX, y: newValue, width: width, height: height) }
62 | }
63 |
64 | public var midY: CGFloat {
65 | get { return frame.midY }
66 | set { frame = CGRect(x: minX, y: newValue - height * 0.5, width: width, height: height) }
67 | }
68 |
69 | public var centerY: CGFloat {
70 | get { return frame.midY }
71 | set { frame = CGRect(x: minX, y: newValue - height * 0.5, width: width, height: height) }
72 | }
73 |
74 | public var maxY: CGFloat {
75 | get { return minY + height }
76 | set { frame = CGRect(x: minX, y: newValue - height, width: width, height: height) }
77 | }
78 |
79 | public var bottom: CGFloat {
80 | get { return minY + height }
81 | set { frame = CGRect(x: minX, y: newValue - height, width: width, height: height) }
82 | }
83 |
84 | public var width: CGFloat {
85 | get { return bounds.width }
86 | set { frame = CGRect(x: minX, y: minY, width: newValue, height: height) }
87 | }
88 |
89 | public var height: CGFloat {
90 | get { return bounds.height }
91 | set { frame = CGRect(x: minX, y: minY, width: width, height: newValue) }
92 | }
93 |
94 | public var center: CGPoint {
95 | set { frame = CGRect(x: newValue.x - frame.size.width * 0.5, y: newValue.y - frame.size.height * 0.5, width: width, height: height) }
96 | get { return CGPoint(x: origin.x + size.width * 0.5, y: origin.y + size.height * 0.5) }
97 | }
98 | }
99 |
100 | public extension CALayer {
101 | public var snapshotImage: UIImage? {
102 | UIGraphicsBeginImageContextWithOptions(bounds.size, isOpaque, 0)
103 | guard let context = UIGraphicsGetCurrentContext() else { return nil }
104 | render(in: context)
105 | let image = UIGraphicsGetImageFromCurrentImageContext()
106 | UIGraphicsEndImageContext()
107 | return image
108 | }
109 |
110 | public func setShadow(color: UIColor, offset: CGSize, radius: CGFloat) {
111 | shadowColor = color.cgColor
112 | shadowOffset = offset
113 | shadowRadius = radius
114 | shadowOpacity = 1
115 | shouldRasterize = true
116 | rasterizationScale = UIScreen.main.scale
117 | }
118 |
119 | public func removeAllSublayers() {
120 | sublayers?.forEach { (sender) in
121 | sender.removeFromSuperlayer()
122 | }
123 | }
124 | }
125 |
126 |
127 |
128 |
--------------------------------------------------------------------------------
/Sources/Classes/CGD.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CGD.swift
3 | // Copyright (c) 2015-2016 Red Rain (http://mochxiao.com).
4 | //
5 | // Permission is hereby granted, free of charge, to any person obtaining a copy
6 | // of this software and associated documentation files (the "Software"), to deal
7 | // in the Software without restriction, including without limitation the rights
8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | // copies of the Software, and to permit persons to whom the Software is
10 | // furnished to do so, subject to the following conditions:
11 | //
12 | // The above copyright notice and this permission notice shall be included in
13 | // all copies or substantial portions of the Software.
14 | //
15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | // THE SOFTWARE.
22 | //
23 |
24 | import Foundation
25 |
26 | // MARK: - Delay Task & Cancel
27 |
28 | public typealias Task = ((_ cancel: Bool) -> ())
29 |
30 | @discardableResult
31 | public func delay(interval time: TimeInterval, task: @escaping (() -> ())) -> Task? {
32 | func dispatch_later(_ block: @escaping () -> ()) {
33 | DispatchQueue.main.asyncAfter(
34 | deadline: .now() + time,
35 | execute: block
36 | )
37 | }
38 |
39 | var closure: (() -> ())? = task
40 | var result: Task?
41 |
42 | let delayedClosure: Task = {
43 | if let internalClosure = closure, !$0 {
44 | DispatchQueue.main.async(execute: internalClosure)
45 | }
46 |
47 | closure = nil
48 | result = nil
49 | }
50 |
51 | result = delayedClosure
52 |
53 | dispatch_later {
54 | if let delayedClosure = result {
55 | delayedClosure(false)
56 | }
57 | }
58 |
59 | return result
60 | }
61 |
62 | public func cancel(_ task: Task?) {
63 | task?(true)
64 | }
65 |
66 | // MARK: - Async Task
67 |
68 | public func mainThreadAsync(execute work: @escaping () -> ()) {
69 | if Thread.isMainThread {
70 | work()
71 | } else {
72 | DispatchQueue.main.async(execute: work)
73 | }
74 | }
75 |
76 | public func globalThreadAsync(execute work: @escaping () -> ()) {
77 | if !Thread.isMainThread {
78 | work()
79 | } else {
80 | DispatchQueue.global().async(execute: work)
81 | }
82 | }
83 |
84 | // MARK: - AsyncSerialWorker
85 |
86 | open class AsyncSerialWorker {
87 | private let serialQueue = DispatchQueue(label: "com.mochxiao.queue.serial")
88 |
89 | open func enqueue(work: @escaping (@escaping () -> ()) -> ()) {
90 | serialQueue.async {
91 | let semaphore = DispatchSemaphore(value: 0)
92 | work { semaphore.signal() }
93 | semaphore.wait()
94 | }
95 | }
96 | }
97 |
98 | // MARK: - LimitedWorker
99 |
100 | open class LimitedWorker {
101 | fileprivate let concurrentQueue = DispatchQueue(label: "com.mochxiao.queue.concurrent", attributes: .concurrent)
102 | fileprivate let semaphore: DispatchSemaphore
103 |
104 | public init(limit: Int) {
105 | semaphore = DispatchSemaphore(value: limit)
106 | }
107 |
108 | open func enqueue(work: @escaping () -> ()) {
109 | concurrentQueue.async {
110 | self.semaphore.wait()
111 | work()
112 | self.semaphore.signal()
113 | }
114 | }
115 | }
116 |
117 | // MARK: - IdentityMap
118 |
119 | public protocol Identifiable {
120 | var identifier: String { get }
121 | }
122 |
123 | extension Identifiable {
124 | var identifier: String { return UUID().uuidString }
125 | }
126 |
127 | extension NSObject: Identifiable {
128 | public var identifier: String { return String(hash) }
129 | }
130 |
131 | open class IdentityMap {
132 | var dictionary = [String: T]()
133 | let accessQueue = DispatchQueue(label: "com.mochxiao.isolation.queue", attributes: .concurrent)
134 |
135 | open subscript(identifier: String) -> T? {
136 | var result: T? = nil
137 | accessQueue.sync { result = self.dictionary[identifier] as T? }
138 | return result
139 | }
140 |
141 | func add(_ object: T) {
142 | accessQueue.async(flags: .barrier) {
143 | self.dictionary[object.identifier] = object
144 | }
145 | }
146 | }
147 |
148 |
--------------------------------------------------------------------------------
/Sources/Extension/UIColor+Extension.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIColor+Extension.swift
3 | // Copyright (c) 2015-2016 Red Rain (http://mochxiao.com).
4 | //
5 | // Permission is hereby granted, free of charge, to any person obtaining a copy
6 | // of this software and associated documentation files (the "Software"), to deal
7 | // in the Software without restriction, including without limitation the rights
8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | // copies of the Software, and to permit persons to whom the Software is
10 | // furnished to do so, subject to the following conditions:
11 | //
12 | // The above copyright notice and this permission notice shall be included in
13 | // all copies or substantial portions of the Software.
14 | //
15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | // THE SOFTWARE.
22 | //
23 |
24 | import UIKit
25 |
26 | public extension UIColor {
27 | public class var random: UIColor {
28 | let red = CGFloat(ExtensionKit.random(in: 0 ..< 255))
29 | let green = CGFloat(ExtensionKit.random(in: 0 ..< 255))
30 | let blue = CGFloat(ExtensionKit.random(in: 0 ..< 255))
31 | return UIColor.make(red: red, green: green, blue: blue)
32 | }
33 |
34 | public class func make(red: CGFloat, green: CGFloat, blue: CGFloat, alpha: CGFloat = 100) -> UIColor {
35 | return UIColor(red: red / 255.0, green: green / 255.0, blue: blue / 255.0, alpha: alpha / 100)
36 | }
37 |
38 | public class func make(hex: String, alpha: CGFloat = 100) -> UIColor {
39 | // Convert hex string to an integer
40 | var hexint: UInt32 = 0
41 |
42 | // Create scanner
43 | let scanner = Scanner(string: hex)
44 |
45 | // Tell scanner to skip the # character
46 | scanner.charactersToBeSkipped = CharacterSet(charactersIn: "#")
47 | scanner.scanHexInt32(&hexint)
48 |
49 | // Create color object, specifying alpha
50 | if hex.count <= 4 {
51 | let divisor = CGFloat(15)
52 | let red = CGFloat((hexint & 0xF00) >> 8) / divisor
53 | let green = CGFloat((hexint & 0x0F0) >> 4) / divisor
54 | let blue = CGFloat( hexint & 0x00F ) / divisor
55 | return UIColor(red: red, green: green, blue: blue, alpha: alpha / 100)
56 | } else {
57 | let divisor = CGFloat(255)
58 | let red = CGFloat((hexint & 0xFF0000) >> 16) / divisor
59 | let green = CGFloat((hexint & 0xFF00 ) >> 8) / divisor
60 | let blue = CGFloat( hexint & 0xFF ) / divisor
61 | return UIColor(red: red, green: green, blue: blue, alpha: alpha / 100)
62 | }
63 | }
64 | }
65 |
66 | public func UIColorFrom(red: CGFloat, green: CGFloat, blue: CGFloat, alpha: CGFloat = 100) -> UIColor {
67 | return UIColor.make(red: red, green: green, blue: blue, alpha: alpha)
68 | }
69 |
70 | public func UIColorFrom(hex: String, alpha: CGFloat = 100) -> UIColor {
71 | return UIColor.make(hex: hex, alpha: alpha)
72 | }
73 |
74 | // MARK: - iOS default color
75 |
76 | public extension UIColor {
77 | public class var tint: UIColor {
78 | // 3, 122, 255, 100
79 | return UIColor.make(hex: "037AFF")
80 | }
81 |
82 | public class var separator: UIColor {
83 | // 200, 199, 204, 100
84 | return UIColor.make(hex: "C8C7CC")
85 | }
86 |
87 | public class var separatorDark: UIColor {
88 | // 69, 75, 65, 100
89 | return UIColor.make(hex: "454b41")
90 | }
91 |
92 | /// Grouped table view background.
93 | public class var groupedBackground: UIColor {
94 | // 239, 239, 244, 100
95 | return UIColor.make(hex: "EFEFF4")
96 | }
97 |
98 | /// Activity background
99 | public class var activityBackground: UIColor {
100 | // 248, 248, 248, 60
101 | return UIColor.make(hex: "F8F8F8", alpha: 60)
102 | }
103 |
104 | public class var disclosureIndicator: UIColor {
105 | return UIColor.make(hex: "C7C7CC")
106 | }
107 |
108 | /// Navigation bar title.
109 | public class var naviTitle: UIColor {
110 | // 3, 3, 3, 100
111 | return UIColor.make(hex: "030303")
112 | }
113 |
114 | public class var subTitle: UIColor {
115 | // 144, 144, 148, 100
116 | return UIColor.make(hex: "909094")
117 | }
118 |
119 | public class var placeholder: UIColor {
120 | // 200, 200, 205, 100
121 | return UIColor.make(hex: "C8C8CD")
122 | }
123 | }
124 |
--------------------------------------------------------------------------------
/Sources/Extension/UIFont+Extension.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIFont+Extension.swift
3 | // Copyright (c) 2015-2016 Red Rain (http://mochxiao.com).
4 | //
5 | // Permission is hereby granted, free of charge, to any person obtaining a copy
6 | // of this software and associated documentation files (the "Software"), to deal
7 | // in the Software without restriction, including without limitation the rights
8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | // copies of the Software, and to permit persons to whom the Software is
10 | // furnished to do so, subject to the following conditions:
11 | //
12 | // The above copyright notice and this permission notice shall be included in
13 | // all copies or substantial portions of the Software.
14 | //
15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | // THE SOFTWARE.
22 | //
23 |
24 | import UIKit
25 |
26 | // MARK: - Fonts
27 |
28 | public extension UIKit.UIFont {
29 | public class var size8Fixed: UIFont {
30 | return UIFont.systemFont(ofSize: 8.0)
31 | }
32 |
33 | public class var size9Fixed: UIFont {
34 | return UIFont.systemFont(ofSize: 9.0)
35 | }
36 |
37 | public class var size10Fixed: UIFont {
38 | return UIFont.systemFont(ofSize: 10.0)
39 | }
40 |
41 | public class var caption2: UIFont {
42 | return UIFont.preferredFont(forTextStyle: UIFontTextStyle.caption2)
43 | }
44 |
45 | public class var size11: UIFont {
46 | return UIFont.caption2
47 | }
48 |
49 | public class var caption1: UIFont {
50 | return UIFont.preferredFont(forTextStyle: UIFontTextStyle.caption1)
51 | }
52 |
53 | public class var size12: UIFont {
54 | return UIFont.caption1
55 | }
56 |
57 | public class var footnote: UIFont {
58 | return UIFont.preferredFont(forTextStyle: UIFontTextStyle.footnote)
59 | }
60 |
61 | public class var size13: UIFont {
62 | return UIFont.footnote
63 | }
64 |
65 | public class var size14Fixed: UIFont {
66 | return UIFont.systemFont(ofSize: 14.0)
67 | }
68 |
69 | public class var subheadline: UIFont {
70 | return UIFont.preferredFont(forTextStyle: UIFontTextStyle.subheadline)
71 | }
72 |
73 | public class var size15: UIFont {
74 | return UIFont.subheadline
75 | }
76 |
77 | public class var body: UIFont {
78 | return UIFont.preferredFont(forTextStyle: UIFontTextStyle.body)
79 | }
80 |
81 | public class var size17: UIFont {
82 | return UIFont.body
83 | }
84 |
85 | public class var headline: UIFont {
86 | return UIFont.preferredFont(forTextStyle: UIFontTextStyle.headline)
87 | }
88 |
89 | public class var size17Blod: UIFont {
90 | return UIFont.headline
91 | }
92 |
93 | public class var size18Fixed: UIFont {
94 | return UIFont.systemFont(ofSize: 18.0)
95 | }
96 |
97 | public class var size19Fixed: UIFont {
98 | return UIFont.systemFont(ofSize: 19.0)
99 | }
100 |
101 | public class var size20Fixed: UIFont {
102 | return UIFont.systemFont(ofSize: 20.0)
103 | }
104 |
105 | public class var size21Fixed: UIFont {
106 | return UIFont.systemFont(ofSize: 21.0)
107 | }
108 |
109 | public class var size22Fixed: UIFont {
110 | return UIFont.systemFont(ofSize: 22.0)
111 | }
112 |
113 | public class var size23Fixed: UIFont {
114 | return UIFont.systemFont(ofSize: 23.0)
115 | }
116 |
117 | public class var size24Fixed: UIFont {
118 | return UIFont.systemFont(ofSize: 24.0)
119 | }
120 |
121 | public class var size26Fixed: UIFont {
122 | return UIFont.systemFont(ofSize: 26.0)
123 | }
124 |
125 | public class var size28Fixed: UIFont {
126 | return UIFont.systemFont(ofSize: 28.0)
127 | }
128 |
129 | public class var size30Fixed: UIFont {
130 | return UIFont.systemFont(ofSize: 30.0)
131 | }
132 |
133 | public class var size32Fixed: UIFont {
134 | return UIFont.systemFont(ofSize: 32.0)
135 | }
136 |
137 | public class var size34Fixed: UIFont {
138 | return UIFont.systemFont(ofSize: 34.0)
139 | }
140 |
141 | public class var size36Fixed: UIFont {
142 | return UIFont.systemFont(ofSize: 36.0)
143 | }
144 |
145 | public class var size38Fixed: UIFont {
146 | return UIFont.systemFont(ofSize: 38.0)
147 | }
148 |
149 | public class var size40Fixed: UIFont {
150 | return UIFont.systemFont(ofSize: 40.0)
151 | }
152 |
153 | // MARK: - available
154 |
155 | public class var callout: UIFont {
156 | guard #available(iOS 9.0, *) else { fatalError("Not supported for current system version.") }
157 | return UIFont.preferredFont(forTextStyle: UIFontTextStyle.callout)
158 | }
159 |
160 | public class var title3: UIFont {
161 | guard #available(iOS 9.0, *) else { fatalError("Not supported for current system version.") }
162 | return UIFont.preferredFont(forTextStyle: UIFontTextStyle.title3)
163 | }
164 |
165 | public class var title2: UIFont {
166 | guard #available(iOS 9.0, *) else { fatalError("Not supported for current system version.") }
167 | return UIFont.preferredFont(forTextStyle: UIFontTextStyle.title2)
168 | }
169 |
170 | public class var title1: UIFont {
171 | guard #available(iOS 9.0, *) else { fatalError("Not supported for current system version.") }
172 | return UIFont.preferredFont(forTextStyle: UIFontTextStyle.title1)
173 | }
174 | }
175 |
--------------------------------------------------------------------------------
/Sources/Classes/BadgeView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // BadgeView.swift
3 | // Copyright (c) 2015-2016 Red Rain (http://mochxiao.com).
4 | //
5 | // Permission is hereby granted, free of charge, to any person obtaining a copy
6 | // of this software and associated documentation files (the "Software"), to deal
7 | // in the Software without restriction, including without limitation the rights
8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | // copies of the Software, and to permit persons to whom the Software is
10 | // furnished to do so, subject to the following conditions:
11 | //
12 | // The above copyright notice and this permission notice shall be included in
13 | // all copies or substantial portions of the Software.
14 | //
15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | // THE SOFTWARE.
22 | //
23 |
24 | import UIKit
25 |
26 | final public class BadgeView: UIView {
27 | fileprivate let fixedHeight: CGFloat = 18
28 | public var textColor: UIColor = UIColor.white
29 |
30 | let badgeLabel: UILabel = {
31 | let label = UILabel()
32 | label.textColor = UIColor.white
33 | label.font = UIFont.preferredFont(forTextStyle: UIFontTextStyle.footnote)
34 | label.lineBreakMode = .byTruncatingTail
35 | label.textAlignment = .center
36 | return label
37 | }()
38 |
39 | public var badgeValue: String = "" {
40 | didSet {
41 | badgeLabel.text = badgeValue
42 | var width: CGFloat = badgeValue.layoutSize(font: badgeLabel.font).width + fixedHeight / 2
43 | if width < fixedHeight {
44 | width = fixedHeight
45 | }
46 |
47 | self.width = ceil(width)
48 | self.height = fixedHeight
49 | displayIfNeeded()
50 | }
51 | }
52 |
53 | fileprivate func displayIfNeeded() {
54 | isHidden = badgeValue.count == 0 || badgeValue == "0"
55 | if !isHidden {
56 | setNeedsDisplay()
57 | invalidateIntrinsicContentSize()
58 | }
59 | }
60 |
61 | public override var intrinsicContentSize : CGSize {
62 | return CGSize(width: bounds.width, height: bounds.height)
63 | }
64 |
65 | public override func draw(_ rect: CGRect) {
66 | badgeLabel.backgroundColor = backgroundColor
67 | layer.cornerRadius = fixedHeight / 2
68 | layer.masksToBounds = true
69 | layer.shouldRasterize = true
70 | layer.rasterizationScale = UIScreen.main.scale
71 | badgeLabel.backgroundColor?.setFill()
72 | UIRectFill(rect)
73 | badgeLabel.drawText(in: rect)
74 | }
75 | }
76 |
77 | // MARK:
78 |
79 | public extension UIView {
80 | fileprivate struct AssociatedKey {
81 | static var badgeView: String = "badgeView"
82 | }
83 |
84 | fileprivate var badgeView: BadgeView? {
85 | get { return associatedObject(forKey: &AssociatedKey.badgeView) as? BadgeView }
86 | set { associate(assignObject: newValue, forKey: &AssociatedKey.badgeView) }
87 | }
88 |
89 | public var badge: String? {
90 | get { return badgeView?.badgeValue }
91 | set {
92 | if nil == badgeView {
93 | addBadgeView()
94 | }
95 | badgeView?.badgeValue = newValue ?? ""
96 | badgeView?.isUserInteractionEnabled = false
97 | }
98 | }
99 |
100 | public var badgeBackgroundColor: UIColor? {
101 | get { return badgeView?.backgroundColor }
102 | set {
103 | if nil == badgeView {
104 | addBadgeView()
105 | }
106 | badgeView?.backgroundColor = newValue
107 | }
108 | }
109 |
110 | public var badgeColor: UIColor? {
111 | get { return badgeView?.textColor }
112 | set {
113 | if nil == badgeView {
114 | addBadgeView()
115 | }
116 | badgeView?.textColor = newValue ?? UIColor.white
117 | }
118 | }
119 |
120 | public func setBadgeOffset(_ offset: UIOffset) {
121 | if nil == badgeView {
122 | addBadgeView()
123 | }
124 | guard let badgeView = badgeView else { return }
125 |
126 | var needsRemove = [NSLayoutConstraint]()
127 | for cons in constraints {
128 | if cons.firstItem as? NSObject == badgeView {
129 | needsRemove.append(cons)
130 | }
131 | }
132 | removeConstraints(needsRemove)
133 |
134 | addConstraint(NSLayoutConstraint(
135 | item: badgeView,
136 | attribute: .centerX,
137 | relatedBy: .equal,
138 | toItem: self,
139 | attribute: .right,
140 | multiplier: 1,
141 | constant: offset.horizontal
142 | ))
143 | addConstraint(NSLayoutConstraint(
144 | item: badgeView,
145 | attribute: .centerY,
146 | relatedBy: .equal,
147 | toItem: self,
148 | attribute: .top,
149 | multiplier: 1,
150 | constant: offset.vertical
151 | ))
152 | }
153 |
154 | fileprivate func addBadgeView() {
155 | let badgeView = BadgeView()
156 | badgeView.translatesAutoresizingMaskIntoConstraints = false
157 | badgeView.backgroundColor = UIColor.red
158 |
159 | addSubview(badgeView)
160 | addConstraint(NSLayoutConstraint(
161 | item: badgeView,
162 | attribute: .centerX,
163 | relatedBy: .equal,
164 | toItem: self,
165 | attribute: .right,
166 | multiplier: 1,
167 | constant: 0
168 | ))
169 | addConstraint(NSLayoutConstraint(
170 | item: badgeView,
171 | attribute: .centerY,
172 | relatedBy: .equal,
173 | toItem: self,
174 | attribute: .top,
175 | multiplier: 1,
176 | constant: 0
177 | ))
178 | self.badgeView = badgeView
179 | }
180 |
181 | }
182 |
183 |
184 |
--------------------------------------------------------------------------------
/Sources/Extension/UIScrollView+Extension.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIScrollView+Extension.swift
3 | // Copyright (c) 2015-2016 Red Rain (http://mochxiao.com).
4 | //
5 | // Permission is hereby granted, free of charge, to any person obtaining a copy
6 | // of this software and associated documentation files (the "Software"), to deal
7 | // in the Software without restriction, including without limitation the rights
8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | // copies of the Software, and to permit persons to whom the Software is
10 | // furnished to do so, subject to the following conditions:
11 | //
12 | // The above copyright notice and this permission notice shall be included in
13 | // all copies or substantial portions of the Software.
14 | //
15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | // THE SOFTWARE.
22 | //
23 |
24 | import UIKit
25 |
26 | public extension UIKit.UIScrollView {
27 | public var insetTop: CGFloat {
28 | get { return contentInset.top }
29 | set {
30 | var inset = contentInset
31 | inset.top = newValue
32 | contentInset = inset
33 | }
34 | }
35 |
36 | public var insetLeft: CGFloat {
37 | get { return contentInset.left }
38 | set {
39 | var inset = contentInset
40 | inset.left = newValue
41 | contentInset = inset
42 | }
43 | }
44 |
45 | public var insetBottom: CGFloat {
46 | get { return contentInset.bottom }
47 | set {
48 | var inset = contentInset
49 | inset.bottom = newValue
50 | contentInset = inset
51 | }
52 | }
53 |
54 | public var insetRight: CGFloat {
55 | get { return contentInset.right }
56 | set {
57 | var inset = contentInset
58 | inset.right = newValue
59 | contentInset = inset
60 | }
61 | }
62 |
63 | public var scrollIndicatorInsetTop: CGFloat {
64 | get { return scrollIndicatorInsets.top }
65 | set {
66 |
67 | var inset = scrollIndicatorInsets
68 | inset.top = newValue
69 | scrollIndicatorInsets = inset
70 | }
71 | }
72 |
73 | public var scrollIndicatorInsetLeft: CGFloat {
74 | get { return scrollIndicatorInsets.left }
75 | set {
76 | var inset = scrollIndicatorInsets
77 | inset.left = newValue
78 | scrollIndicatorInsets = inset
79 | }
80 | }
81 |
82 | public var scrollIndicatorInsetBottom: CGFloat {
83 | get { return scrollIndicatorInsets.bottom }
84 | set {
85 | var inset = scrollIndicatorInsets
86 | inset.bottom = newValue
87 | scrollIndicatorInsets = inset
88 | }
89 | }
90 |
91 | public var scrollIndicatorInsetRight: CGFloat {
92 | get { return scrollIndicatorInsets.right }
93 | set {
94 | var inset = scrollIndicatorInsets
95 | inset.right = newValue
96 | scrollIndicatorInsets = inset
97 | }
98 | }
99 |
100 | public var contentOffsetX: CGFloat {
101 | get { return contentOffset.x }
102 | set {
103 | var offset = contentOffset
104 | offset.x = newValue
105 | contentOffset = offset
106 | }
107 | }
108 |
109 | public var contentOffsetY: CGFloat {
110 | get { return contentOffset.y }
111 | set {
112 | var offset = contentOffset
113 | offset.y = newValue
114 | contentOffset = offset
115 | }
116 | }
117 |
118 | public var contentSizeWidth: CGFloat {
119 | get { return contentSize.width }
120 | set {
121 | var size = contentSize
122 | size.width = newValue
123 | contentSize = size
124 | }
125 | }
126 |
127 | public var contentSizeHeight: CGFloat {
128 | get { return contentSize.height }
129 | set {
130 | var size = contentSize
131 | size.height = newValue
132 | contentSize = size
133 | }
134 | }
135 | }
136 |
137 | // MARK: - RefreshControl
138 |
139 | public extension UIScrollView {
140 | fileprivate struct AssociationKey {
141 | fileprivate static var refreshControl: String = "com.mochxiao.uiscrollview.RefreshControl"
142 | }
143 |
144 | public private(set) var refreshContrl: UIRefreshControl? {
145 | get { return associatedObject(forKey: &AssociationKey.refreshControl) as? UIRefreshControl }
146 | set { associate(assignObject: newValue, forKey: &AssociationKey.refreshControl) }
147 | }
148 |
149 | public func addRefreshControl(withActionHandler handler: @escaping ((UIScrollView) -> ())) {
150 | if let _ = refreshContrl {
151 | return
152 | }
153 |
154 | let _refreshContrl = UIRefreshControl(frame: CGRect(
155 | x: 0,
156 | y: 0,
157 | width: UIScreen.main.bounds.width,
158 | height: 64
159 | ))
160 | addSubview(_refreshContrl)
161 | sendSubview(toBack: _refreshContrl)
162 | _refreshContrl.addControlEvents(.valueChanged) { [weak self] (_) in
163 | if let this = self {
164 | if this.refreshControlEnabled {
165 | handler(this)
166 | } else {
167 | this.endRefreshing()
168 | }
169 | }
170 | }
171 | refreshContrl = _refreshContrl
172 | }
173 |
174 | public var refreshControlEnabled: Bool {
175 | get {
176 | if let refreshContrl = refreshContrl {
177 | return refreshContrl.isEnabled
178 | }
179 | return false
180 | }
181 | set {
182 | if let refreshContrl = refreshContrl {
183 | refreshContrl.isEnabled = newValue
184 | refreshContrl.alpha = newValue ? 1 : 0
185 | }
186 | }
187 | }
188 |
189 | public func beginRefreshing() {
190 | refreshContrl?.beginRefreshing()
191 | }
192 |
193 | public func endRefreshing() {
194 | refreshContrl?.endRefreshing()
195 | }
196 |
197 | public var refreshing: Bool {
198 | if let refreshContrl = refreshContrl {
199 | return refreshContrl.isRefreshing
200 | }
201 | return false
202 | }
203 | }
204 |
205 | // MARK: - For YYLabel touch hightlight
206 |
207 | public extension UIScrollView {
208 | public func commitTouchesImmediately() {
209 | delaysContentTouches = false
210 | canCancelContentTouches = true
211 |
212 | if let wrapView = subviews.first {
213 | if NSStringFromClass(type(of: wrapView)).hasSuffix("WrapperView") {
214 | if let gestureRecognizers = wrapView.gestureRecognizers {
215 | for gesture in gestureRecognizers {
216 | if NSStringFromClass(type(of: gesture)).contains("DelayedTouchesBegan") {
217 | gesture.isEnabled = false
218 | break
219 | }
220 | }
221 | }
222 | }
223 | }
224 | }
225 | }
226 |
--------------------------------------------------------------------------------
/Sources/Extension/UIControl+Extension.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIControl+Extension.swift
3 | // Copyright (c) 2015-2016 Red Rain (http://mochxiao.com).
4 | //
5 | // Permission is hereby granted, free of charge, to any person obtaining a copy
6 | // of this software and associated documentation files (the "Software"), to deal
7 | // in the Software without restriction, including without limitation the rights
8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | // copies of the Software, and to permit persons to whom the Software is
10 | // furnished to do so, subject to the following conditions:
11 | //
12 | // The above copyright notice and this permission notice shall be included in
13 | // all copies or substantial portions of the Software.
14 | //
15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | // THE SOFTWARE.
22 | //
23 |
24 | import UIKit
25 |
26 | private struct AssociationKey {
27 | fileprivate static var touchDownHandlerWrapper: String = "com.mochxiao.uicontrol.touchDownHandlerWrapper"
28 | fileprivate static var touchDownRepeatHandlerWrapper: String = "com.mochxiao.uicontrol.touchDownRepeatHandlerWrapper"
29 | fileprivate static var touchDragInsideHandlerWrapper: String = "com.mochxiao.uicontrol.touchDragInsideHandlerWrapper"
30 | fileprivate static var touchDragOutsideHandlerWrapper: String = "com.mochxiao.uicontrol.touchDragOutsideHandlerWrapper"
31 | fileprivate static var touchDragEnterHandlerWrapper: String = "com.mochxiao.uicontrol.touchDragEnterHandlerWrapper"
32 | fileprivate static var touchDragExitHandlerWrapper: String = "com.mochxiao.uicontrol.touchDragExitHandlerWrapper"
33 | fileprivate static var touchUpInsideHandlerWrapper: String = "com.mochxiao.uicontrol.touchUpInsideHandlerWrapper"
34 | fileprivate static var touchUpOutsideHandlerWrapper: String = "com.mochxiao.uicontrol.touchUpOutsideHandlerWrapper"
35 | fileprivate static var touchCancelHandlerWrapper: String = "com.mochxiao.uicontrol.touchCancelHandlerWrapper"
36 | fileprivate static var valueChangedHandlerWrapper: String = "com.mochxiao.uicontrol.valueChangedHandlerWrapper"
37 | fileprivate static var primaryActionTriggeredHandlerWrapper: String = "com.mochxiao.uicontrol.primaryActionTriggeredHandlerWrapper"
38 | fileprivate static var editingDidBeginHandlerWrapper: String = "com.mochxiao.uicontrol.editingDidBeginHandlerWrapper"
39 | fileprivate static var editingChangedHandlerWrapper: String = "com.mochxiao.uicontrol.editingChangedHandlerWrapper"
40 | fileprivate static var editingDidEndHandlerWrapper: String = "com.mochxiao.uicontrol.editingDidEndHandlerWrapper"
41 | fileprivate static var editingDidEndOnExitHandlerWrapper: String = "com.mochxiao.uicontrol.editingDidEndOnExitHandlerWrapper"
42 | fileprivate static var allTouchEventsHandlerWrapper: String = "com.mochxiao.uicontrol.allTouchEventsHandlerWrapper"
43 | fileprivate static var allEditingEventsHandlerWrapper: String = "com.mochxiao.uicontrol.allEditingEventsHandlerWrapper"
44 | fileprivate static var applicationReservedHandlerWrapper: String = "com.mochxiao.uicontrol.applicationReservedHandlerWrapper"
45 | fileprivate static var systemReservedHandlerWrapper: String = "com.mochxiao.uicontrol.systemReservedHandlerWrapper"
46 | fileprivate static var allEventsHandlerWrapper: String = "com.mochxiao.uicontrol.allEventsHandlerWrapper"
47 | }
48 |
49 | // MARK: - UIControl Action
50 |
51 | public protocol UIControlActionFunctionProtocol {}
52 | extension UIControl: UIControlActionFunctionProtocol {}
53 |
54 | public extension UIControlActionFunctionProtocol where Self: UIControl {
55 | public func addControlEvents(_ events: UIControlEvents, handler: @escaping (Self) -> ()) {
56 | let trampoline = ActionTrampoline(action: handler)
57 | addTarget(trampoline, action: NSSelectorFromString("action:"), for: events)
58 | associate(object: trampoline, forEvents: events)
59 | }
60 |
61 | fileprivate func associate(object: AnyObject, forEvents events: UIControlEvents) {
62 | if events.contains(.touchDown) {
63 | associate(retainObject: object, forKey: &AssociationKey.touchDownHandlerWrapper)
64 | }
65 | if events.contains(.touchDownRepeat) {
66 | associate(retainObject: object, forKey: &AssociationKey.touchDownRepeatHandlerWrapper)
67 | }
68 | if events.contains(.touchDragInside) {
69 | associate(retainObject: object, forKey: &AssociationKey.touchDragInsideHandlerWrapper)
70 | }
71 | if events.contains(.touchDragOutside) {
72 | associate(retainObject: object, forKey: &AssociationKey.touchDragOutsideHandlerWrapper)
73 | }
74 | if events.contains(.touchDragEnter) {
75 | associate(retainObject: object, forKey: &AssociationKey.touchDragEnterHandlerWrapper)
76 | }
77 | if events.contains(.touchDragExit) {
78 | associate(retainObject: object, forKey: &AssociationKey.touchDragExitHandlerWrapper)
79 | }
80 | if events.contains(.touchUpInside) {
81 | associate(retainObject: object, forKey: &AssociationKey.touchUpInsideHandlerWrapper)
82 | }
83 | if events.contains(.touchUpOutside) {
84 | associate(retainObject: object, forKey: &AssociationKey.touchUpOutsideHandlerWrapper)
85 | }
86 | if events.contains(.touchCancel) {
87 | associate(retainObject: object, forKey: &AssociationKey.touchCancelHandlerWrapper)
88 | }
89 | if events.contains(.valueChanged) {
90 | associate(retainObject: object, forKey: &AssociationKey.valueChangedHandlerWrapper)
91 | }
92 | if events.contains(.editingDidBegin) {
93 | associate(retainObject: object, forKey: &AssociationKey.editingDidBeginHandlerWrapper)
94 | }
95 | if events.contains(.editingChanged) {
96 | associate(retainObject: object, forKey: &AssociationKey.editingChangedHandlerWrapper)
97 | }
98 | if events.contains(.editingDidEnd) {
99 | associate(retainObject: object, forKey: &AssociationKey.editingDidEndHandlerWrapper)
100 | }
101 | if events.contains(.allTouchEvents) {
102 | associate(retainObject: object, forKey: &AssociationKey.allTouchEventsHandlerWrapper)
103 | }
104 | if events.contains(.editingDidEndOnExit) {
105 | associate(retainObject: object, forKey: &AssociationKey.editingDidEndOnExitHandlerWrapper)
106 | }
107 | if events.contains(.allEditingEvents) {
108 | associate(retainObject: object, forKey: &AssociationKey.allEditingEventsHandlerWrapper)
109 | }
110 | if events.contains(.applicationReserved) {
111 | associate(retainObject: object, forKey: &AssociationKey.applicationReservedHandlerWrapper)
112 | }
113 | if events.contains(.systemReserved) {
114 | associate(retainObject: object, forKey: &AssociationKey.systemReservedHandlerWrapper)
115 | }
116 | if events.contains(.allEvents) {
117 | associate(retainObject: object, forKey: &AssociationKey.allEventsHandlerWrapper)
118 | }
119 |
120 | if #available(iOS 9.0, *) {
121 | if events.contains(.primaryActionTriggered) {
122 | associate(retainObject: object, forKey: &AssociationKey.primaryActionTriggeredHandlerWrapper)
123 | }
124 | }
125 | }
126 | }
127 |
--------------------------------------------------------------------------------
/Sources/Extension/Lang+Extension.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Lang+Extension.swift
3 | // Copyright (c) 2015-2016 Red Rain (http://mochxiao.com).
4 | //
5 | // Permission is hereby granted, free of charge, to any person obtaining a copy
6 | // of this software and associated documentation files (the "Software"), to deal
7 | // in the Software without restriction, including without limitation the rights
8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | // copies of the Software, and to permit persons to whom the Software is
10 | // furnished to do so, subject to the following conditions:
11 | //
12 | // The above copyright notice and this permission notice shall be included in
13 | // all copies or substantial portions of the Software.
14 | //
15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | // THE SOFTWARE.
22 | //
23 |
24 | import Foundation
25 |
26 | public func +(lhs: UIEdgeInsets, rhs: UIEdgeInsets) -> UIEdgeInsets {
27 | return UIEdgeInsetsMake(lhs.top + rhs.top, lhs.left + rhs.left, lhs.bottom + rhs.bottom, lhs.right + rhs.right)
28 | }
29 |
30 | /// Find out the geiven object is some type or not.
31 | public func objectIsType(_ object: Any, _ someObjectOfType: T.Type) -> Bool {
32 | return object is T
33 | }
34 |
35 | /**
36 | See: https://gist.githubusercontent.com/Abizern/a81f31a75e1ad98ff80d/raw/85b85cbb9bcdeb8cdbf2521ac935e3d2cb4cdd4f/loggingPrint.swift
37 |
38 | Prints the filename, function name, line number and textual representation of `object` and a newline character into
39 | the standard output if the build setting for "Other Swift Flags" defines `-D DEBUG`.
40 |
41 | The current thread is a prefix on the output. for the main thread, for anything else.
42 |
43 | Only the first parameter needs to be passed to this funtion.
44 |
45 | The textual representation is obtained from the `object` using its protocol conformances, in the following
46 | order of preference: `CustomDebugStringConvertible` and `CustomStringConvertible`. Do not overload this function for
47 | your type. Instead, adopt one of the protocols mentioned above.
48 |
49 | :param: object The object whose textual representation will be printed. If this is an expression, it is lazily evaluated.
50 | :param: file The name of the file, defaults to the current file without the ".swift" extension.
51 | :param: function The name of the function, defaults to the function within which the call is made.
52 | :param: line The line number, defaults to the line number within the file that the call is made.
53 | */
54 | public func logging(_ object: @autoclosure () -> T, _ file: String = #file, _ function: String = #function, _ line: Int = #line) {
55 | #if DEBUG
56 | let value = object()
57 | let stringRepresentation: String
58 |
59 | if let value = value as? CustomDebugStringConvertible {
60 | stringRepresentation = value.debugDescription
61 | } else if let value = value as? CustomStringConvertible {
62 | stringRepresentation = value.description
63 | } else {
64 | stringRepresentation = "\(value)"
65 | }
66 | let fileURL = URL(string: file)?.lastPathComponent ?? "Unknown file"
67 | let queue = Thread.isMainThread ? "UI" : "BG"
68 | print("<\(queue)> \(fileURL) \(function)[\(line)]: " + stringRepresentation)
69 | #endif
70 | }
71 |
72 | /// Get value from `any` instance like KVC
73 | public func takeValue(from object: Any, forKey key: String) -> Any? {
74 | func takeValue(_ mirror: Mirror, _ key: String) -> Any? {
75 | for (targetKey, targetMirror) in mirror.children {
76 | if key == targetKey {
77 | return targetMirror
78 | }
79 | }
80 | return nil
81 | }
82 |
83 | func take(_ mirror: Mirror, _ key: String) -> Any? {
84 | if let value = takeValue(mirror, key) {
85 | return value
86 | }
87 | if let superMirror = mirror.superclassMirror {
88 | return take(superMirror, key)
89 | }
90 | return nil
91 | }
92 |
93 | let mirror = Mirror(reflecting: object)
94 | return take(mirror, key)
95 | }
96 |
97 | /// Generate random number in range
98 | public func random(in range: Range) -> Int {
99 | let count = UInt32(range.upperBound - range.lowerBound)
100 | return Int(arc4random_uniform(count)) + range.lowerBound
101 | }
102 |
103 | // MARK: - synchronized
104 |
105 | public func synchronized(lock: AnyObject, work: () -> ()) {
106 | objc_sync_enter(lock)
107 | work()
108 | objc_sync_exit(lock)
109 | }
110 |
111 | // MARK: - Method swizzle
112 |
113 | /// Should be placed in dispatch_once
114 | public func swizzleInstanceMethod(
115 | for cls: AnyClass,
116 | original: Selector,
117 | override: Selector)
118 | {
119 | guard let originalMethod = class_getInstanceMethod(cls, original) else { return }
120 | guard let overrideMethod = class_getInstanceMethod(cls, override) else { return }
121 |
122 | if class_addMethod(
123 | cls,
124 | original,
125 | method_getImplementation(overrideMethod),
126 | method_getTypeEncoding(overrideMethod))
127 | {
128 | class_replaceMethod(
129 | cls,
130 | override,
131 | method_getImplementation(originalMethod),
132 | method_getTypeEncoding(originalMethod)
133 | )
134 | } else {
135 | method_exchangeImplementations(originalMethod, overrideMethod)
136 | }
137 | }
138 |
139 | /// Should be placed in dispatch_once
140 | public func swizzleClassMethod(
141 | for cls: AnyClass,
142 | original: Selector,
143 | override: Selector)
144 | {
145 | guard let originalMethod = class_getClassMethod(cls, original) else { return }
146 | guard let overrideMethod = class_getClassMethod(cls, override) else { return }
147 |
148 | if class_addMethod(
149 | cls,
150 | original,
151 | method_getImplementation(overrideMethod),
152 | method_getTypeEncoding(overrideMethod))
153 | {
154 | class_replaceMethod(
155 | cls,
156 | override,
157 | method_getImplementation(originalMethod),
158 | method_getTypeEncoding(originalMethod)
159 | )
160 | } else {
161 | method_exchangeImplementations(originalMethod, overrideMethod)
162 | }
163 | }
164 |
165 | // MARK: - C Pointers
166 |
167 | /// Convert a `void *` type to Swift type, use this function carefully
168 | public func convertUnsafePointerToSwiftType(_ value: UnsafeRawPointer) -> T {
169 | return value.assumingMemoryBound(to: T.self).pointee
170 | }
171 |
172 | // MARK: - Sandbox
173 |
174 | private func searchPath(for directory: FileManager.SearchPathDirectory) -> String? {
175 | return NSSearchPathForDirectoriesInDomains(directory, FileManager.SearchPathDomainMask.userDomainMask, true).first
176 | }
177 |
178 | public func directoryForDocument() -> String? {
179 | return searchPath(for: .documentDirectory)
180 | }
181 |
182 | public func directoryForCache() -> String? {
183 | return searchPath(for: .cachesDirectory)
184 | }
185 |
186 | public func directoryForDownloads() -> String? {
187 | return searchPath(for: .downloadsDirectory)
188 | }
189 |
190 | public func directoryForMovies() -> String? {
191 | return searchPath(for: .moviesDirectory)
192 | }
193 |
194 | public func directoryForMusic() -> String? {
195 | return searchPath(for: .musicDirectory)
196 | }
197 |
198 | public func directoryForPictures() -> String? {
199 | return searchPath(for: .picturesDirectory)
200 | }
201 |
202 | // MARK: - ClosureDecorator
203 |
204 | /// ClosureDecorator, make use closure like a NSObject, aka objc_asscoiateXXX.
205 | final public class ClosureDecorator: NSObject {
206 | fileprivate let closure: Any
207 |
208 | fileprivate override init() {
209 | fatalError("Use init(action:) instead.")
210 | }
211 |
212 | public init(_ closure: @escaping (() -> ())) {
213 | self.closure = closure
214 | }
215 |
216 | public init(_ closure: @escaping ((T) -> ())) {
217 | self.closure = closure
218 | }
219 |
220 | public func invoke(_ param: T) {
221 | if let closure = closure as? (() -> ()) {
222 | closure()
223 | } else if let closure = closure as? ((T) -> ()) {
224 | closure(param)
225 | }
226 | }
227 |
228 | deinit {
229 | logging("\(#file):\(#line):\(type(of: self)):\(#function)")
230 | }
231 | }
232 |
233 | // MARK: - Swifty Target & Action
234 | // See: https://www.mikeash.com/pyblog/friday-qa-2015-12-25-swifty-targetaction.html
235 |
236 | final public class ActionTrampoline: NSObject {
237 | fileprivate let action: ((T) -> ())
238 | public var selector: Selector? {
239 | return NSSelectorFromString("action:")
240 | }
241 |
242 | public init(action: @escaping ((T) -> ())) {
243 | self.action = action
244 | }
245 |
246 | @objc public func action(_ sender: AnyObject) {
247 | // UIControl: add(target: AnyObject?, action: Selector, forControlEvents controlEvents: UIControlEvents)
248 | if let sender = sender as? T {
249 | action(sender)
250 | }
251 | // UIGestureRecognizer: add(target: AnyObject, action: Selector)
252 | else if let sender = sender as? UIGestureRecognizer {
253 | action(sender.view as! T)
254 | }
255 | }
256 |
257 | deinit {
258 | logging("\(#file):\(#line):\(type(of: self)):\(#function)")
259 | }
260 | }
261 |
262 |
--------------------------------------------------------------------------------
/Sources/Extension/UIButton+Extension.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIButton+Extension.swift
3 | // Copyright (c) 2015-2016 Red Rain (http://mochxiao.com).
4 | //
5 | // Permission is hereby granted, free of charge, to any person obtaining a copy
6 | // of this software and associated documentation files (the "Software"), to deal
7 | // in the Software without restriction, including without limitation the rights
8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | // copies of the Software, and to permit persons to whom the Software is
10 | // furnished to do so, subject to the following conditions:
11 | //
12 | // The above copyright notice and this permission notice shall be included in
13 | // all copies or substantial portions of the Software.
14 | //
15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | // THE SOFTWARE.
22 | //
23 |
24 | import UIKit
25 |
26 | // MARK: - AssociationKey
27 |
28 | private struct AssociationKey {
29 | fileprivate static var activityIndicatorContainerView: String = "com.mochxiao.uibutton.activityIndicatorContainerView"
30 | }
31 |
32 | // MARK: - Property for state
33 |
34 | public extension UIButton {
35 | public var title: String? {
36 | get { return self.title(for: .normal) }
37 | set { setTitle(newValue, for: .normal) }
38 | }
39 |
40 | public var titleFont: UIFont? {
41 | get { return titleLabel?.font }
42 | set { titleLabel?.font = newValue }
43 | }
44 |
45 | public var attributedTitle: NSAttributedString? {
46 | get { return self.attributedTitle(for: .normal) }
47 | set { setAttributedTitle(newValue, for: .normal) }
48 | }
49 |
50 | public var titleColor: UIColor? {
51 | get { return self.titleColor(for: .normal) }
52 | set {
53 | setTitleColor(newValue, for: .normal)
54 | setTitleColor(newValue?.withAlphaComponent(0.5), for: .disabled)
55 | setTitleColor(newValue, for: .selected)
56 | if buttonType == .custom {
57 | setTitleColor(newValue?.withAlphaComponent(0.5), for: .highlighted)
58 | }
59 | }
60 | }
61 |
62 | public var titleShadowColor: UIColor? {
63 | get { return self.titleShadowColor(for: .normal) }
64 | set {
65 | setTitleShadowColor(newValue, for: .normal)
66 | setTitleShadowColor(newValue?.withAlphaComponent(0.5), for: .disabled)
67 | setTitleShadowColor(newValue, for: .selected)
68 | }
69 | }
70 |
71 | public var image: UIImage? {
72 | get { return self.image(for: .normal) }
73 | set {
74 | setImage(newValue?.withRenderingMode(.alwaysOriginal), for: .normal)
75 | }
76 | }
77 |
78 | public var selectedImage: UIImage? {
79 | get { return self.image(for: .selected) }
80 | set { setImage(newValue?.withRenderingMode(.alwaysOriginal), for: .selected) }
81 | }
82 |
83 | public var backgroundImage: UIImage? {
84 | get { return self.backgroundImage(for: .normal) }
85 | set {
86 | let image = newValue?.withRenderingMode(.alwaysOriginal)
87 | setBackgroundImage(image, for: .normal)
88 | if buttonType == .custom {
89 | setBackgroundImage(image?.remake(alpha: 0.5), for: .highlighted)
90 | setBackgroundImage(image?.remake(alpha: 0.5), for: .disabled)
91 | }
92 | }
93 | }
94 |
95 | public var selectedBackgroundImage: UIImage? {
96 | get { return self.backgroundImage(for: .selected) }
97 | set { setBackgroundImage(newValue?.withRenderingMode(.alwaysOriginal), for: .selected) }
98 | }
99 |
100 | public var disabledBackgroundImage: UIImage? {
101 | get { return self.backgroundImage(for: .disabled) }
102 | set { setBackgroundImage(newValue?.withRenderingMode(.alwaysOriginal), for: .disabled) }
103 | }
104 | }
105 |
106 | // MARK: - Image position
107 |
108 | public extension UIButton {
109 | /// Convenience `setImageAlignmentToTop:` setter.
110 | public var imageAlignmentTopSpace: CGFloat {
111 | get { fatalError("Unavailable.") }
112 | set { setImageAlignmentToTop(titleSpace: newValue) }
113 | }
114 |
115 | /// Convenience `setImageAlignmentToLeft:` setter.
116 | public var imageAlignmentLeftSpace: CGFloat {
117 | get { fatalError("Unavailable.") }
118 | set { setImageAlignmentToLeft(titleSpace: newValue) }
119 | }
120 |
121 | /// Convenience `setImageAlignmentToBottom:` setter.
122 | public var imageAlignmentBottomSpace: CGFloat {
123 | get { fatalError("Unavailable.") }
124 | set { setImageAlignmentToBottom(titleSpace: newValue) }
125 | }
126 |
127 | /// Convenience `setImageAlignmentToRight:` setter.
128 | public var imageAlignmentRightSpace: CGFloat {
129 | get { fatalError("Unavailable.") }
130 | set { setImageAlignmentToRight(titleSpace: newValue) }
131 | }
132 |
133 | /// Setup image position relate to title
134 | /// **NOTE**: Before invoke this methods you should setup title and image already
135 | public func setImageAlignmentToTop(titleSpace space: CGFloat = 4.0) {
136 | guard let currentImage = currentImage else { return }
137 | guard let currentTitle = currentTitle as NSString? else { return }
138 | guard let titleLabel = titleLabel else { return }
139 |
140 | let halfSpace = (space / 2.0).ceilling
141 | let halfImageWidth = (currentImage.size.width / 2.0).ceilling
142 | let halfImageHeight = (currentImage.size.height / 2.0).ceilling
143 | titleEdgeInsets = UIEdgeInsetsMake(
144 | halfImageHeight + halfSpace,
145 | -halfImageWidth,
146 | -halfImageHeight - halfSpace,
147 | halfImageWidth
148 | )
149 |
150 | let titleBounds = currentTitle.size(withAttributes: [.font: titleLabel.font]).ceilling
151 | let halfEdgeWidth = (titleBounds.width / 2.0).ceilling
152 | let halfEdgeHeight = (titleBounds.height / 2.0).ceilling
153 | imageEdgeInsets = UIEdgeInsetsMake(
154 | -halfEdgeHeight - halfSpace,
155 | halfEdgeWidth,
156 | halfEdgeHeight + halfSpace,
157 | -halfEdgeWidth
158 | )
159 | }
160 |
161 | /// Setup image position relate to title
162 | /// **NOTE**: Before invoke this methods you should setup title and image already
163 | public func setImageAlignmentToBottom(titleSpace space: CGFloat = 4.0) {
164 | guard let currentImage = currentImage else { return }
165 | guard let currentTitle = currentTitle as NSString? else { return }
166 | guard let titleLabel = titleLabel else { return }
167 |
168 | let halfSpace = (space / 2.0).ceilling
169 | let halfImageWidth = (currentImage.size.width / 2.0).ceilling
170 | let halfImageHeight = (currentImage.size.height / 2.0).ceilling
171 | titleEdgeInsets = UIEdgeInsetsMake(
172 | -halfImageHeight - halfSpace,
173 | -halfImageWidth,
174 | halfImageHeight + halfSpace,
175 | halfImageWidth
176 | )
177 |
178 | let titleBounds = currentTitle.size(withAttributes: [.font: titleLabel.font]).ceilling
179 | let halfEdgeWidth = (titleBounds.width / 2.0).ceilling
180 | let halfEdgeHeight = (titleBounds.height / 2.0).ceilling
181 | imageEdgeInsets = UIEdgeInsetsMake(
182 | halfEdgeHeight + halfSpace,
183 | halfEdgeWidth,
184 | -halfEdgeHeight - halfSpace,
185 | -halfEdgeWidth
186 | )
187 | }
188 |
189 | /// Setup image position relate to title
190 | /// **NOTE**: Before invoke this methods you should setup title and image already
191 | public func setImageAlignmentToLeft(titleSpace space: CGFloat = 4.0) {
192 | let halfSpace = (space / 2.0).ceilling
193 |
194 | titleEdgeInsets = UIEdgeInsetsMake(
195 | 0,
196 | halfSpace,
197 | 0,
198 | -halfSpace
199 | )
200 | imageEdgeInsets = UIEdgeInsetsMake(
201 | 0,
202 | -halfSpace,
203 | 0,
204 | halfSpace
205 | )
206 | }
207 |
208 | /// Setup image position relate to title
209 | /// **NOTE**: Before invoke this methods you should setup title and image already
210 | public func setImageAlignmentToRight(titleSpace space: CGFloat = 4.0) {
211 | guard let currentImage = currentImage else { return }
212 | guard let currentTitle = currentTitle as NSString? else { return }
213 | guard let titleLabel = titleLabel else { return }
214 |
215 | let halfSpace = (space / 2.0).ceilling
216 | let imageWidth = currentImage.size.width.ceilling
217 | let edgeWidth = currentTitle.size(withAttributes: [.font: titleLabel.font]).width.ceilling
218 |
219 | titleEdgeInsets = UIEdgeInsetsMake(
220 | 0,
221 | -imageWidth - halfSpace,
222 | 0,
223 | imageWidth + halfSpace
224 | )
225 | imageEdgeInsets = UIEdgeInsetsMake(
226 | 0,
227 | edgeWidth + halfSpace,
228 | 0,
229 | -edgeWidth - halfSpace
230 | )
231 | }
232 | }
233 |
234 | // MARK: -
235 |
236 | public extension UIButton {
237 | public func performToggleSelectStateImageAnimation() {
238 | guard let normalImage = self.image(for: .normal) else { return }
239 | guard let selectedImage = self.image(for: .selected) else { return }
240 | guard let _imageView = imageView else { return }
241 |
242 | // Clear image
243 | {
244 | setImage(nil, for: .normal)
245 | setImage(nil, for: .selected)
246 | }()
247 |
248 | let animatedImageView = UIImageView(image: isSelected ? selectedImage : normalImage)
249 | animatedImageView.frame = _imageView.frame
250 | addSubview(animatedImageView)
251 |
252 | let recover = {
253 | UIView.animate(withDuration: 0.2, delay: 0, options: .beginFromCurrentState, animations: {
254 | animatedImageView.transform = CGAffineTransform.identity
255 | }, completion: { (finished: Bool) in
256 | self.setImage(normalImage, for: .normal)
257 | self.setImage(selectedImage, for: .selected)
258 | self.isSelected = !self.isSelected
259 | animatedImageView.removeFromSuperview()
260 | })
261 | }
262 |
263 | let zoomOut = {
264 | animatedImageView.image = !self.isSelected ? selectedImage : normalImage
265 | UIView.animate(withDuration: 0.2, delay: 0, options: .beginFromCurrentState, animations: {
266 | animatedImageView.transform = CGAffineTransform(scaleX: 0.9, y: 0.9)
267 | }, completion: { (finished: Bool) in
268 | recover()
269 | })
270 | }
271 |
272 | // Start with zoom in
273 | UIView.animate(withDuration: 0.2, delay: 0, options: .beginFromCurrentState, animations: {
274 | animatedImageView.transform = CGAffineTransform(scaleX: 1.7, y: 1.7)
275 | }, completion: { (finished: Bool) in
276 | zoomOut()
277 | })
278 | }
279 | }
280 |
--------------------------------------------------------------------------------
/Sources/Extension/UIViewController+Extension.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIViewController+Extension.swift
3 | // Copyright (c) 2015-2016 Red Rain (http://mochxiao.com).
4 | //
5 | // Permission is hereby granted, free of charge, to any person obtaining a copy
6 | // of this software and associated documentation files (the "Software"), to deal
7 | // in the Software without restriction, including without limitation the rights
8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | // copies of the Software, and to permit persons to whom the Software is
10 | // furnished to do so, subject to the following conditions:
11 | //
12 | // The above copyright notice and this permission notice shall be included in
13 | // all copies or substantial portions of the Software.
14 | //
15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | // THE SOFTWARE.
22 | //
23 |
24 | import UIKit
25 |
26 | // MARK: - AssociationKey
27 |
28 | private struct AssociationKey {
29 | fileprivate static var tag: String = "com.mochxiao.uialertaction.tag"
30 | fileprivate static var imagePickerCompletionHandlerWrapper = "com.mochxiao.uiimagepickercontroller.imagePickerCompletionHandlerWrapper"
31 | }
32 |
33 | // MARK: - Present UIAlertController
34 |
35 | public extension UIAlertAction {
36 | /// Default value is -1.
37 | public fileprivate(set) var tag: Int {
38 | get {
39 | if let value = associatedObject(forKey: &AssociationKey.tag) as? Int {
40 | return value
41 | }
42 | return -1
43 | }
44 | set { associate(retainObject: newValue, forKey: &AssociationKey.tag) }
45 | }
46 | }
47 |
48 | public extension UIViewController {
49 | /// Present error.
50 | public func presentError(_ error: NSError) {
51 | if let message = error.userInfo[NSLocalizedDescriptionKey] as? String {
52 | presentAlert(message: message)
53 | } else if let message = error.userInfo[NSLocalizedFailureReasonErrorKey] as? String {
54 | presentAlert(message: message)
55 | } else {
56 | presentAlert(message: error.localizedDescription)
57 | }
58 | }
59 |
60 | /// Present message.
61 | public func presentAlert(
62 | title: String = "",
63 | message: String,
64 | cancelTitle: String = "好",
65 | cancelHandler: ((UIAlertAction) -> ())? = nil,
66 | otherTitles: [String]? = nil,
67 | othersHandler: ((UIAlertAction) -> ())? = nil)
68 | {
69 | let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert)
70 | let cancelAction = UIAlertAction(title: cancelTitle, style: .cancel, handler: cancelHandler)
71 | alertController.addAction(cancelAction)
72 |
73 | if let otherTitles = otherTitles {
74 | for (index, title) in otherTitles.enumerated() {
75 | let action = UIAlertAction(title: title, style: .default, handler: othersHandler)
76 | action.tag = index
77 | alertController.addAction(action)
78 | }
79 | }
80 |
81 | present(alertController, animated: true, completion: nil)
82 | }
83 |
84 | /// Present ActionSheet.
85 | public func presentActionSheet(
86 | title: String = "",
87 | message: String,
88 | cancelTitle: String = "取消",
89 | cancelHandler: ((UIAlertAction) -> ())? = nil,
90 | actionTitles: [String],
91 | actionHandler: ((UIAlertAction) -> ())? = nil)
92 | {
93 | let alertController = UIAlertController(title: title, message: message, preferredStyle: .actionSheet)
94 | for (index, title) in actionTitles.enumerated() {
95 | let action = UIAlertAction(title: title, style: .default, handler: actionHandler)
96 | action.tag = index
97 | alertController.addAction(action)
98 | }
99 | let cancelAction = UIAlertAction(title: cancelTitle, style: .cancel, handler: cancelHandler)
100 | alertController.addAction(cancelAction)
101 | present(alertController, animated: true, completion: nil)
102 | }
103 | }
104 |
105 | /// The convience version of `presentAlert(title:message:cancelTitle:cancelHandler:)`.
106 | /// Use this func carefully, it maybe iterate many times.
107 | public func doPresentAlert(
108 | title: String = "",
109 | message: String,
110 | cancelTitle: String = "好",
111 | cancelHandler: ((UIAlertAction) -> ())? = nil)
112 | {
113 | findLastPresentedViewController()?.presentAlert(
114 | title: title,
115 | message: message,
116 | cancelTitle: cancelTitle,
117 | cancelHandler: cancelHandler
118 | )
119 | }
120 |
121 | public func doPresentAlert(
122 | title: String = "",
123 | message: String,
124 | cancelTitle: String = "好",
125 | cancelHandler: ((UIAlertAction) -> ())? = nil,
126 | actionTitles: [String],
127 | actionHandler: ((UIAlertAction) -> ())? = nil)
128 | {
129 | findLastPresentedViewController()?.presentAlert(
130 | title: title,
131 | message: message,
132 | cancelTitle: cancelTitle,
133 | cancelHandler: cancelHandler,
134 | otherTitles: actionTitles,
135 | othersHandler: actionHandler
136 | )
137 | }
138 |
139 | /// The convience version of `presentError:`.
140 | /// Use this func carefully, it maybe iterate many times.
141 | public func doPresentError(_ error: NSError) {
142 | findLastPresentedViewController()?.presentError(error)
143 | }
144 |
145 | /// The convience version of `presentActionSheet(title:message:cancelTitle:cancelHandler:actionTitles:actionHandler:)`.
146 | /// Use this func carefully, it maybe iterate many times.
147 | public func doPresentActionSheet(
148 | title: String = "",
149 | message: String,
150 | cancelTitle: String = "取消",
151 | cancelHandler: ((UIAlertAction) -> ())? = nil,
152 | actionTitles:[String],
153 | actionHandler:((UIAlertAction) -> ())? = nil)
154 | {
155 | findLastPresentedViewController()?.presentActionSheet(
156 | title: title,
157 | message: message,
158 | cancelTitle: cancelTitle,
159 | cancelHandler: cancelHandler,
160 | actionTitles: actionTitles,
161 | actionHandler: actionHandler
162 | )
163 | }
164 |
165 | // MARK: - Find last time presented view controller
166 |
167 | /// Returns the most recently presented UIViewController (visible).
168 | /// http://stackoverflow.com/questions/24825123/get-the-current-view-controller-from-the-app-delegate
169 | public func findLastPresentedViewController() -> UIViewController? {
170 | func findTopLevelViewController(_ viewController: UIViewController) -> UIViewController? {
171 | if let vc = viewController.presentedViewController {
172 | return findTopLevelViewController(vc)
173 | } else if let vc = viewController as? UISplitViewController {
174 | if let vc = vc.viewControllers.last {
175 | return findTopLevelViewController(vc)
176 | }
177 | return vc
178 | } else if let vc = viewController as? UINavigationController {
179 | if let vc = vc.topViewController {
180 | return findTopLevelViewController(vc)
181 | }
182 | return vc
183 | } else if let vc = viewController as? UITabBarController {
184 | if let vc = vc.selectedViewController {
185 | return findTopLevelViewController(vc)
186 | }
187 | return vc
188 | } else {
189 | return viewController
190 | }
191 | }
192 |
193 | if let rootViewController = UIApplication.shared.keyWindow?.rootViewController {
194 | return findTopLevelViewController(rootViewController)
195 | }
196 |
197 | return nil
198 | }
199 |
200 | // MARK: - Present UIImagePickerController
201 |
202 | private extension UIImagePickerController {
203 | var imagePickerCompletionHandlerWrapper: ClosureDecorator<(UIImagePickerController, UIImage?)> {
204 | get { return associatedObject(forKey: &AssociationKey.imagePickerCompletionHandlerWrapper) as! ClosureDecorator<(UIImagePickerController, UIImage?)> }
205 | set { associate(retainObject: newValue, forKey: &AssociationKey.imagePickerCompletionHandlerWrapper) }
206 | }
207 | }
208 |
209 | public extension UIViewController {
210 | /// Present UIImagePickerController.
211 | public func presentImagePicker(sourceType: UIImagePickerControllerSourceType = .photoLibrary, completionHandler: @escaping ((UIImagePickerController, UIImage?) -> ())) {
212 | let imagePicker = UIImagePickerController()
213 | imagePicker.sourceType = sourceType
214 | imagePicker.videoQuality = .typeLow
215 | imagePicker.delegate = self
216 | imagePicker.allowsEditing = true
217 | imagePicker.view.tintColor = UIApplication.shared.keyWindow?.tintColor
218 | imagePicker.imagePickerCompletionHandlerWrapper = ClosureDecorator(completionHandler)
219 | present(imagePicker, animated: true, completion: nil)
220 | }
221 | }
222 |
223 | extension UIViewController: UINavigationControllerDelegate, UIImagePickerControllerDelegate {
224 | public func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
225 | picker.presentingViewController?.dismiss(animated: true, completion: nil)
226 | picker.imagePickerCompletionHandlerWrapper.invoke((picker, nil))
227 | }
228 |
229 | public func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
230 | globalThreadAsync { () -> Void in
231 | if let image = info[UIImagePickerControllerEditedImage] as? UIImage {
232 | let newImage = image.orientation(to: .up)
233 | if let imageData = newImage.compress(toByte: 100 * 1024) {
234 | let resultImage = UIImage(data: imageData, scale: UIScreen.main.scale)
235 | mainThreadAsync {
236 | picker.presentingViewController?.dismiss(animated: true, completion: nil)
237 | picker.imagePickerCompletionHandlerWrapper.invoke((picker, resultImage))
238 | }
239 | return
240 | }
241 | }
242 | }
243 | }
244 |
245 | public func imagePickerController(_ picker: UIImagePickerController, didFinishPickingImage image: UIImage, editingInfo: [String : AnyObject]?) {
246 | picker.presentingViewController?.dismiss(animated: true, completion: nil)
247 | picker.imagePickerCompletionHandlerWrapper.invoke((picker, image))
248 | }
249 | }
250 |
251 | // MARK: - Navigation
252 |
253 | public extension UIViewController {
254 | public func show(_ viewController: UIViewController) {
255 | navigationController?.show(viewController, sender: self)
256 | }
257 |
258 | public func backToPrevious(animated: Bool = true) {
259 | if let presentingViewController = presentingViewController {
260 | presentingViewController.dismiss(animated: animated, completion: nil)
261 | } else {
262 | _ = navigationController?.popViewController(animated: animated)
263 | }
264 | }
265 |
266 | public func backToRoot(animated: Bool = true) {
267 | if let presentingViewController = presentingViewController {
268 | presentingViewController.dismiss(animated: animated, completion: nil)
269 | } else {
270 | _ = navigationController?.popToRootViewController(animated: animated)
271 | }
272 | }
273 |
274 | public func present(_ viewControllerToPresent: UIViewController, completion: @escaping (() -> ())) {
275 | present(viewControllerToPresent, animated: true, completion: completion)
276 | }
277 |
278 | public func present(_ viewControllerToPresent: UIViewController) {
279 | present(viewControllerToPresent, animated: true, completion: nil)
280 | }
281 |
282 | public func presentTranslucent(_ viewController: UIViewController, modalTransitionStyle: UIModalTransitionStyle = .coverVertical, animated flag: Bool = true, completion: (() -> ())? = nil) {
283 | viewController.modalPresentationStyle = .custom
284 | viewController.modalTransitionStyle = UIDevice.iOS8x ? modalTransitionStyle : .crossDissolve
285 | // Very important
286 | view.window?.rootViewController?.modalPresentationStyle = UIDevice.iOS8x ? .fullScreen : .currentContext
287 | present(viewController, animated: flag, completion: completion)
288 | }
289 |
290 | public func dismiss(completion: (() -> Void)? = nil) {
291 | presentingViewController?.dismiss(animated: true, completion: completion)
292 | }
293 |
294 | public func dismissToTop(animated: Bool = true, completion: (() -> Void)? = nil) {
295 | var presentedViewController = self
296 | while let presentingViewController = presentedViewController.presentingViewController {
297 | presentedViewController = presentingViewController
298 | }
299 | presentedViewController.dismiss(animated: animated, completion: completion)
300 | }
301 |
302 | public func addChild(_ viewController: UIViewController) {
303 | viewController.willMove(toParentViewController: self)
304 | addChildViewController(viewController)
305 | viewController.view.frame = view.frame
306 | view.addSubview(viewController.view)
307 | viewController.didMove(toParentViewController: self)
308 | }
309 | }
310 |
311 | public extension UIViewController {
312 | public func showRightBarButtonItem(withImage image: UIImage?, actionHandler: ((UIBarButtonItem) -> ())?) {
313 | navigationItem.rightBarButtonItem = UIBarButtonItem.make(image: image, actionHandler: actionHandler)
314 | }
315 |
316 | public func showRightBarButtonItem(withTitle title: String, actionHandler: ((UIBarButtonItem) -> ())?) {
317 | navigationItem.rightBarButtonItem = UIBarButtonItem.make(title: title, actionHandler: actionHandler)
318 | }
319 |
320 | public func showRightBarButtonItem(withSystemItem item: UIBarButtonSystemItem, actionHandler: ((UIBarButtonItem) -> ())?) {
321 | navigationItem.rightBarButtonItem = UIBarButtonItem.make(systemItem: item, actionHandler: actionHandler)
322 | }
323 |
324 | public func showLeftBarButtonItem(withImage image: UIImage?, actionHandler: ((UIBarButtonItem) -> ())?) {
325 | navigationItem.leftBarButtonItem = UIBarButtonItem.make(image: image, actionHandler: actionHandler)
326 | }
327 |
328 | public func showLeftBarButtonItem(withTitle title: String, actionHandler: ((UIBarButtonItem) -> ())?) {
329 | navigationItem.leftBarButtonItem = UIBarButtonItem.make(title: title, actionHandler: actionHandler)
330 | }
331 |
332 | public func showLeftBarButtonItem(withSystemItem item: UIBarButtonSystemItem, actionHandler: ((UIBarButtonItem) -> ())?) {
333 | navigationItem.leftBarButtonItem = UIBarButtonItem.make(systemItem: item, actionHandler: actionHandler)
334 | }
335 | }
336 |
--------------------------------------------------------------------------------
/Sources/Extension/String+Extension.swift:
--------------------------------------------------------------------------------
1 | //
2 | // String+Extension.swift
3 | // Copyright (c) 2015-2016 Red Rain (http://mochxiao.com).
4 | //
5 | // Permission is hereby granted, free of charge, to any person obtaining a copy
6 | // of this software and associated documentation files (the "Software"), to deal
7 | // in the Software without restriction, including without limitation the rights
8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | // copies of the Software, and to permit persons to whom the Software is
10 | // furnished to do so, subject to the following conditions:
11 | //
12 | // The above copyright notice and this permission notice shall be included in
13 | // all copies or substantial portions of the Software.
14 | //
15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | // THE SOFTWARE.
22 | //
23 |
24 | import UIKit
25 |
26 | // MARK: - URL
27 | /// Stolen from Alamofire
28 | public extension String {
29 | public var escaped: String {
30 | let string: String = self
31 | let generalDelimitersToEncode = ":#[]@" // does not include "?" or "/" due to RFC 3986 - Section 3.4
32 | let subDelimitersToEncode = "!$&'()*+,;="
33 |
34 | var allowedCharacterSet = CharacterSet.urlQueryAllowed
35 | allowedCharacterSet.remove(charactersIn: "\(generalDelimitersToEncode)\(subDelimitersToEncode)")
36 |
37 | var escaped = ""
38 |
39 | if #available(iOS 8.3, *) {
40 | escaped = string.addingPercentEncoding(withAllowedCharacters: allowedCharacterSet) ?? string
41 | } else {
42 | let batchSize = 50
43 | var index = string.startIndex
44 |
45 | while index != string.endIndex {
46 | let startIndex = index
47 | let endIndex = string.index(index, offsetBy: batchSize, limitedBy: string.endIndex) ?? string.endIndex
48 | let range = startIndex.. Bool {
66 | return NSPredicate(format: "SELF MATCHES %@", regEx).evaluate(with: self)
67 | }
68 |
69 | public var isPhoneNumber: Bool {
70 | return isMatch(regEx: "^(1[345789])\\d{9}")
71 | }
72 |
73 | public var isEmail: Bool {
74 | return isMatch(regEx: "^\\w+([-+.]\\w+)*@\\w+([-.]\\w+)*\\.\\w+([-.]\\w+)*$")
75 | }
76 | }
77 |
78 | // MARK: -
79 |
80 | public extension String {
81 | public var isEmpty: Bool {
82 | return 0 == trimed.count
83 | }
84 |
85 | public var trimed: String {
86 | return trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
87 | }
88 |
89 | public func layoutSize(font: UIFont, preferredMaxLayoutWidth: CGFloat = UIScreen.width) -> CGSize {
90 | let str = self as NSString
91 | return str.boundingRect(
92 | with: CGSize(width: preferredMaxLayoutWidth, height: CGFloat.greatestFiniteMagnitude),
93 | options: [.usesLineFragmentOrigin, .usesFontLeading, .truncatesLastVisibleLine],
94 | attributes: [.font: font],
95 | context: nil
96 | ).size
97 | }
98 |
99 | public subscript(range: Range) -> String {
100 | return String(self[range])
101 | }
102 |
103 | public func substring(fromIndex minIndex: Int, toIndex maxIndex: Int) -> String {
104 | let start = index(startIndex, offsetBy: minIndex)
105 | let end = index(startIndex, offsetBy: maxIndex, limitedBy: endIndex)
106 | let range = Range(start ..< end!)
107 | return String(self[range])
108 | }
109 |
110 | public func substring(fromIndex minIndex: Int) -> String {
111 | let start = index(startIndex, offsetBy: minIndex)
112 | return String(self[start...])
113 | }
114 |
115 | public func substring(toIndex maxIndex: Int) -> String {
116 | return substring(fromIndex: 0, toIndex: maxIndex)
117 | }
118 |
119 | public static var uniqueIdentifier: String {
120 | return UUID().uuidString.replacingOccurrences(of: "-", with: "")
121 | }
122 | }
123 |
124 | public extension NSAttributedString {
125 | public func layoutSize(preferredMaxLayoutWidth: CGFloat = UIScreen.width) -> CGSize {
126 | return boundingRect(
127 | with: CGSize(width: preferredMaxLayoutWidth, height: CGFloat.greatestFiniteMagnitude),
128 | options: [.usesLineFragmentOrigin, .usesFontLeading, .truncatesLastVisibleLine],
129 | context: nil
130 | ).size
131 | }
132 | }
133 |
134 | // MARK: - String -> Numbers
135 |
136 | public extension String {
137 | /// Convert string to double, shall not input cannot converted string.
138 | public var double: Double {
139 | return Double(self)!
140 | }
141 |
142 | /// Convert string to Float, shall not input cannot converted string.
143 | public var float: Float {
144 | return Float(self)!
145 | }
146 |
147 | /// Convert string to Int, shall not input cannot converted string.
148 | public var int: Int {
149 | return Int(self)!
150 | }
151 | }
152 |
153 | // MARK: - md5
154 | // https://github.com/onevcat/Kingfisher/blob/master/Sources/String%2BMD5.swift
155 | public extension String {
156 | var md5: String {
157 | if let data = self.data(using: .utf8, allowLossyConversion: true) {
158 |
159 | let message = data.withUnsafeBytes { bytes -> [UInt8] in
160 | return Array(UnsafeBufferPointer(start: bytes, count: data.count))
161 | }
162 |
163 | let MD5Calculator = MD5(message)
164 | let MD5Data = MD5Calculator.calculate()
165 |
166 | let MD5String = NSMutableString()
167 | for c in MD5Data {
168 | MD5String.appendFormat("%02x", c)
169 | }
170 | return MD5String as String
171 |
172 | } else {
173 | return self
174 | }
175 | }
176 | }
177 |
178 |
179 | /** array of bytes, little-endian representation */
180 | func arrayOfBytes(_ value: T, length: Int? = nil) -> [UInt8] {
181 | let totalBytes = length ?? (MemoryLayout.size * 8)
182 |
183 | let valuePointer = UnsafeMutablePointer.allocate(capacity: 1)
184 | valuePointer.pointee = value
185 |
186 | let bytes = valuePointer.withMemoryRebound(to: UInt8.self, capacity: totalBytes) { (bytesPointer) -> [UInt8] in
187 | var bytes = [UInt8](repeating: 0, count: totalBytes)
188 | for j in 0...size, totalBytes) {
189 | bytes[totalBytes - 1 - j] = (bytesPointer + j).pointee
190 | }
191 | return bytes
192 | }
193 |
194 | valuePointer.deinitialize()
195 | valuePointer.deallocate(capacity: 1)
196 |
197 | return bytes
198 | }
199 |
200 | extension Int {
201 | /** Array of bytes with optional padding (little-endian) */
202 | func bytes(_ totalBytes: Int = MemoryLayout.size) -> [UInt8] {
203 | return arrayOfBytes(self, length: totalBytes)
204 | }
205 |
206 | }
207 |
208 | extension NSMutableData {
209 |
210 | /** Convenient way to append bytes */
211 | func appendBytes(_ arrayOfBytes: [UInt8]) {
212 | append(arrayOfBytes, length: arrayOfBytes.count)
213 | }
214 |
215 | }
216 |
217 | protocol HashProtocol {
218 | var message: Array { get }
219 |
220 | /** Common part for hash calculation. Prepare header data. */
221 | func prepare(_ len: Int) -> Array
222 | }
223 |
224 | extension HashProtocol {
225 |
226 | func prepare(_ len: Int) -> Array {
227 | var tmpMessage = message
228 |
229 | // Step 1. Append Padding Bits
230 | tmpMessage.append(0x80) // append one bit (UInt8 with one bit) to message
231 |
232 | // append "0" bit until message length in bits ≡ 448 (mod 512)
233 | var msgLength = tmpMessage.count
234 | var counter = 0
235 |
236 | while msgLength % len != (len - 8) {
237 | counter += 1
238 | msgLength += 1
239 | }
240 |
241 | tmpMessage += Array(repeating: 0, count: counter)
242 | return tmpMessage
243 | }
244 | }
245 |
246 | func toUInt32Array(_ slice: ArraySlice) -> Array {
247 | var result = Array()
248 | result.reserveCapacity(16)
249 |
250 | for idx in stride(from: slice.startIndex, to: slice.endIndex, by: MemoryLayout.size) {
251 | let d0 = UInt32(slice[idx.advanced(by: 3)]) << 24
252 | let d1 = UInt32(slice[idx.advanced(by: 2)]) << 16
253 | let d2 = UInt32(slice[idx.advanced(by: 1)]) << 8
254 | let d3 = UInt32(slice[idx])
255 | let val: UInt32 = d0 | d1 | d2 | d3
256 |
257 | result.append(val)
258 | }
259 | return result
260 | }
261 |
262 | struct BytesIterator: IteratorProtocol {
263 |
264 | let chunkSize: Int
265 | let data: [UInt8]
266 |
267 | init(chunkSize: Int, data: [UInt8]) {
268 | self.chunkSize = chunkSize
269 | self.data = data
270 | }
271 |
272 | var offset = 0
273 |
274 | mutating func next() -> ArraySlice? {
275 | let end = min(chunkSize, data.count - offset)
276 | let result = data[offset.. 0 ? result : nil
279 | }
280 | }
281 |
282 | struct BytesSequence: Sequence {
283 | let chunkSize: Int
284 | let data: [UInt8]
285 |
286 | func makeIterator() -> BytesIterator {
287 | return BytesIterator(chunkSize: chunkSize, data: data)
288 | }
289 | }
290 |
291 | func rotateLeft(_ value: UInt32, bits: UInt32) -> UInt32 {
292 | return ((value << bits) & 0xFFFFFFFF) | (value >> (32 - bits))
293 | }
294 |
295 | class MD5: HashProtocol {
296 |
297 | static let size = 16 // 128 / 8
298 | let message: [UInt8]
299 |
300 | init (_ message: [UInt8]) {
301 | self.message = message
302 | }
303 |
304 | /** specifies the per-round shift amounts */
305 | private let shifts: [UInt32] = [7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22,
306 | 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20,
307 | 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23,
308 | 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21]
309 |
310 | /** binary integer part of the sines of integers (Radians) */
311 | private let sines: [UInt32] = [0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee,
312 | 0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501,
313 | 0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be,
314 | 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821,
315 | 0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa,
316 | 0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8,
317 | 0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed,
318 | 0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a,
319 | 0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c,
320 | 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70,
321 | 0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x4881d05,
322 | 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665,
323 | 0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039,
324 | 0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1,
325 | 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1,
326 | 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391]
327 |
328 | private let hashes: [UInt32] = [0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476]
329 |
330 | func calculate() -> [UInt8] {
331 | var tmpMessage = prepare(64)
332 | tmpMessage.reserveCapacity(tmpMessage.count + 4)
333 |
334 | // hash values
335 | var hh = hashes
336 |
337 | // Step 2. Append Length a 64-bit representation of lengthInBits
338 | let lengthInBits = (message.count * 8)
339 | let lengthBytes = lengthInBits.bytes(64 / 8)
340 | tmpMessage += lengthBytes.reversed()
341 |
342 | // Process the message in successive 512-bit chunks:
343 | let chunkSizeBytes = 512 / 8 // 64
344 |
345 | for chunk in BytesSequence(chunkSize: chunkSizeBytes, data: tmpMessage) {
346 | // break chunk into sixteen 32-bit words M[j], 0 ≤ j ≤ 15
347 | var M = toUInt32Array(chunk)
348 | assert(M.count == 16, "Invalid array")
349 |
350 | // Initialize hash value for this chunk:
351 | var A: UInt32 = hh[0]
352 | var B: UInt32 = hh[1]
353 | var C: UInt32 = hh[2]
354 | var D: UInt32 = hh[3]
355 |
356 | var dTemp: UInt32 = 0
357 |
358 | // Main loop
359 | for j in 0 ..< sines.count {
360 | var g = 0
361 | var F: UInt32 = 0
362 |
363 | switch j {
364 | case 0...15:
365 | F = (B & C) | ((~B) & D)
366 | g = j
367 | break
368 | case 16...31:
369 | F = (D & B) | (~D & C)
370 | g = (5 * j + 1) % 16
371 | break
372 | case 32...47:
373 | F = B ^ C ^ D
374 | g = (3 * j + 5) % 16
375 | break
376 | case 48...63:
377 | F = C ^ (B | (~D))
378 | g = (7 * j) % 16
379 | break
380 | default:
381 | break
382 | }
383 | dTemp = D
384 | D = C
385 | C = B
386 | B = B &+ rotateLeft((A &+ F &+ sines[j] &+ M[g]), bits: shifts[j])
387 | A = dTemp
388 | }
389 |
390 | hh[0] = hh[0] &+ A
391 | hh[1] = hh[1] &+ B
392 | hh[2] = hh[2] &+ C
393 | hh[3] = hh[3] &+ D
394 | }
395 |
396 | var result = [UInt8]()
397 | result.reserveCapacity(hh.count / 4)
398 |
399 | hh.forEach {
400 | let itemLE = $0.littleEndian
401 | result += [UInt8(itemLE & 0xff), UInt8((itemLE >> 8) & 0xff), UInt8((itemLE >> 16) & 0xff), UInt8((itemLE >> 24) & 0xff)]
402 | }
403 | return result
404 | }
405 | }
406 |
--------------------------------------------------------------------------------
/Sources/Extension/UIImage+Extension.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIImage+Extension.swift
3 | // Copyright (c) 2015-2016 Red Rain (http://mochxiao.com).
4 | //
5 | // Permission is hereby granted, free of charge, to any person obtaining a copy
6 | // of this software and associated documentation files (the "Software"), to deal
7 | // in the Software without restriction, including without limitation the rights
8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | // copies of the Software, and to permit persons to whom the Software is
10 | // furnished to do so, subject to the following conditions:
11 | //
12 | // The above copyright notice and this permission notice shall be included in
13 | // all copies or substantial portions of the Software.
14 | //
15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | // THE SOFTWARE.
22 | //
23 |
24 | import UIKit
25 |
26 | // MARK: - Load image
27 |
28 | public extension UIImage {
29 | /// Load bundle image with file name
30 | public class func load(fileName: String, extensionType: String = "png") -> UIImage? {
31 | func pathForResource(_ fileName: String, ofType: String) -> String? {
32 | return Bundle.main.path(forResource: fileName, ofType: ofType)
33 | }
34 |
35 | if UIScreen.main.bounds.width > 375.0 {
36 | if let filePath = pathForResource("\(fileName)@3x", ofType: extensionType) {
37 | return UIImage(contentsOfFile: filePath)
38 | }
39 | }
40 |
41 | if let filePath = pathForResource("\(fileName)@2x", ofType: extensionType) {
42 | return UIImage(contentsOfFile: filePath)
43 | }
44 |
45 | if let filePath = pathForResource(fileName, ofType: extensionType) {
46 | return UIImage(contentsOfFile: filePath)
47 | }
48 |
49 | return nil
50 | }
51 | }
52 |
53 | // MARK: - Compress & Decompress
54 |
55 | public extension UIImage {
56 | /// Represent current image to render mode original.
57 | public var original: UIImage {
58 | return withRenderingMode(.alwaysOriginal)
59 | }
60 |
61 | /// Decompressed image.
62 | public var decompressed: UIImage? {
63 | UIGraphicsBeginImageContextWithOptions(size, true, 0)
64 | draw(at: CGPoint.zero)
65 | guard let decompressedImage = UIGraphicsGetImageFromCurrentImageContext() else {
66 | return nil
67 | }
68 | UIGraphicsEndImageContext()
69 | return decompressedImage
70 | }
71 |
72 | /// Compress image quality as possible to fit target size.
73 | public func compressQuality(toByte maxLength: Int) -> Data? {
74 | let image: UIImage = self
75 | var compression: CGFloat = 1
76 | if let data = UIImageJPEGRepresentation(image, compression), data.count < maxLength {
77 | return data
78 | }
79 |
80 | // Compress by quality
81 | var max: CGFloat = 1
82 | var min: CGFloat = 0
83 | var data: Data!
84 | for _ in 0 ..< 6 {
85 | compression = (max + min) / 2
86 | data = UIImageJPEGRepresentation(image, compression)
87 | if nil != data {
88 | if CGFloat(data.count) < CGFloat(maxLength) * 0.9 {
89 | min = compression
90 | } else if data.count > maxLength {
91 | max = compression
92 | } else {
93 | break
94 | }
95 | }
96 | }
97 |
98 | return data
99 | }
100 |
101 | /// Compress image quality & size as possible to fit target size.
102 | func compress(toByte maxLength: Int) -> Data? {
103 | let image: UIImage = self
104 | var compression: CGFloat = 1
105 | if let data = UIImageJPEGRepresentation(image, compression), data.count < maxLength {
106 | return data
107 | }
108 |
109 | // Compress by quality
110 | var max: CGFloat = 1
111 | var min: CGFloat = 0
112 | var data: Data!
113 | for _ in 0 ..< 6 {
114 | compression = (max + min) / 2
115 | data = UIImageJPEGRepresentation(image, compression)
116 | if nil != data {
117 | if CGFloat(data.count) < CGFloat(maxLength) * 0.9 {
118 | min = compression
119 | } else if data.count > maxLength {
120 | max = compression
121 | } else {
122 | break
123 | }
124 | }
125 | }
126 | if data != nil && data.count < maxLength {
127 | return data
128 | }
129 | var resultImage: UIImage! = UIImage(data: data)
130 |
131 | // Compress by size
132 | var lastDataLength: Int = 0
133 | while resultImage != nil && data.count > maxLength, data.count != lastDataLength {
134 | lastDataLength = data.count
135 | let ratio: CGFloat = CGFloat(maxLength) / CGFloat(data.count)
136 | let size: CGSize = CGSize(width: Int(resultImage.size.width * sqrt(ratio)),
137 | height: Int(resultImage.size.height * sqrt(ratio)))
138 | UIGraphicsBeginImageContext(size)
139 | resultImage.draw(in: CGRect(x: 0, y: 0, width: size.width, height: size.height))
140 | resultImage = UIGraphicsGetImageFromCurrentImageContext()!
141 | UIGraphicsEndImageContext()
142 | data = UIImageJPEGRepresentation(resultImage, compression)!
143 | }
144 | return data
145 | }
146 |
147 | public func orientation(to orientation: UIImageOrientation) -> UIImage {
148 | if imageOrientation == orientation {
149 | return self
150 | }
151 |
152 | if let CGImage = cgImage {
153 | return UIImage(cgImage: CGImage, scale: UIScreen.main.scale, orientation: orientation)
154 | }
155 | logging("Cannot complete action.")
156 | return self
157 | }
158 |
159 | public var bytes: Data? {
160 | // Establish color space
161 | let colorSpace = CGColorSpaceCreateDeviceRGB()
162 |
163 | // Establish context
164 | let width: Int = Int(size.width)
165 | let height: Int = Int(size.height)
166 | guard let context = CGContext(
167 | data: nil,
168 | width: width,
169 | height: height,
170 | bitsPerComponent: 8,
171 | bytesPerRow: width * 4,
172 | space: colorSpace,
173 | bitmapInfo: CGImageAlphaInfo.premultipliedFirst.rawValue
174 | ) else { return nil }
175 |
176 | // Draw source into context bytes
177 | let rect = CGRectMake(size: size)
178 | guard let CGImage = cgImage else {
179 | return nil
180 | }
181 | context.draw(CGImage, in: rect)
182 |
183 | // Create NSData from bytes
184 | if let data = context.data {
185 | return Data(bytes: UnsafeMutableRawPointer(data), count: (width * height * 4))
186 | } else {
187 | return nil
188 | }
189 | }
190 |
191 | public func color(atPixel point: CGPoint) -> UIColor? {
192 | let width = size.width
193 | let height = size.height
194 | if !CGRect(x: 0, y: 0, width: width, height: height).contains(point) {
195 | return nil
196 | }
197 |
198 | let bytesPerPixel = 4
199 | let bytesPerRow = bytesPerPixel * 1
200 | let colorSpace = CGColorSpaceCreateDeviceRGB()
201 | let bitsPerComponent = 8
202 | var pixelData: [CGFloat] = [0, 0, 0, 0]
203 |
204 | guard let context = CGContext(
205 | data: &pixelData,
206 | width: 1,
207 | height: 1,
208 | bitsPerComponent: bitsPerComponent,
209 | bytesPerRow: bytesPerRow,
210 | space: colorSpace,
211 | bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue
212 | ) else { return nil }
213 |
214 | let pointX = trunc(point.x)
215 | let pointY = trunc(point.y)
216 | guard let cgImage = cgImage else { return nil }
217 | context.translateBy(x: -pointX, y: pointY - height)
218 | context.draw(cgImage, in: CGRect(x: 0, y: 0, width: width, height: height))
219 | return UIColor(red: pixelData[0] / 255.0, green: pixelData[1] / 255.0, blue: pixelData[2] / 255.0, alpha: pixelData[3] / 255.0)
220 | }
221 | }
222 |
223 | // MARK: - Draw
224 |
225 | public extension UIImage {
226 | public class func make(
227 | color: UIColor,
228 | size: CGSize = CGSize(width: 1, height: 1),
229 | roundingCorners: UIRectCorner = .allCorners,
230 | radius: CGFloat = 0,
231 | strokeColor: UIColor = UIColor.clear,
232 | strokeLineWidth: CGFloat = 0) -> UIImage?
233 | {
234 | let rect = CGRect(origin: CGPoint(x: 0, y: 0), size: size)
235 | UIGraphicsBeginImageContextWithOptions(rect.size, false, 0)
236 |
237 | guard let context = UIGraphicsGetCurrentContext() else { fatalError() }
238 |
239 | context.setFillColor(color.cgColor)
240 | context.setLineWidth(strokeLineWidth)
241 | context.setStrokeColor(strokeColor.cgColor)
242 |
243 | let roundedRect = CGRect(
244 | x: strokeLineWidth,
245 | y: strokeLineWidth,
246 | width: rect.width - strokeLineWidth * 2,
247 | height: rect.height - strokeLineWidth * 2)
248 | let path = UIBezierPath(
249 | roundedRect: roundedRect,
250 | byRoundingCorners: roundingCorners,
251 | cornerRadii: CGSize(width: radius, height: radius)
252 | )
253 | context.addPath(path.cgPath)
254 |
255 | context.drawPath(using: .fillStroke)
256 |
257 | guard let output = UIGraphicsGetImageFromCurrentImageContext() else {
258 | return nil
259 | }
260 | UIGraphicsEndImageContext()
261 | return output
262 | }
263 |
264 | public func remake(
265 | roundingCorners corners: UIRectCorner = .allCorners,
266 | radius: CGFloat = 0,
267 | strokeColor: UIColor? = nil,
268 | strokeLineWidth: CGFloat = 0,
269 | stockLineJoin: CGLineJoin = .miter) -> UIImage
270 | {
271 | UIGraphicsBeginImageContextWithOptions(size, false, 0)
272 | defer { UIGraphicsEndImageContext() }
273 | guard let context = UIGraphicsGetCurrentContext() else {
274 | return self
275 | }
276 | context.scaleBy(x: 1, y: -1)
277 | context.translateBy(x: 0, y: -size.height)
278 |
279 | let roundedRect = CGRect(origin: CGPoint(x: 0, y: 0), size: size)
280 | let sideLength = min(roundedRect.size.width, roundedRect.size.height)
281 | if strokeLineWidth < sideLength * 0.5 {
282 | let roundedpath = UIBezierPath(
283 | roundedRect: roundedRect.insetBy(dx: strokeLineWidth, dy: strokeLineWidth),
284 | byRoundingCorners: corners,
285 | cornerRadii: CGSize(width: radius, height: strokeLineWidth)
286 | )
287 | roundedpath.close()
288 |
289 | context.saveGState()
290 | context.addPath(roundedpath.cgPath)
291 | context.clip()
292 | context.draw(cgImage!, in: roundedRect)
293 | context.restoreGState()
294 | }
295 |
296 | if nil != strokeColor && strokeLineWidth > 0 {
297 | let strokeInset = (floor(strokeLineWidth * scale) + 0.5) / scale
298 | let strokeRect = roundedRect.insetBy(dx: strokeInset, dy: strokeInset)
299 | let strokeRadius = radius > scale / 2.0 ? radius - scale / 2.0 : 0.0
300 | let strokePath = UIBezierPath(
301 | roundedRect: strokeRect,
302 | byRoundingCorners: corners,
303 | cornerRadii: CGSize(width: strokeRadius, height: strokeLineWidth)
304 | )
305 | strokePath.close()
306 |
307 | context.saveGState()
308 | context.setStrokeColor(strokeColor!.cgColor)
309 | context.setLineWidth(strokeLineWidth)
310 | context.setLineJoin(stockLineJoin)
311 | context.addPath(strokePath.cgPath)
312 | context.strokePath()
313 | context.restoreGState()
314 | }
315 |
316 | if let output = UIGraphicsGetImageFromCurrentImageContext() {
317 | return output
318 | }
319 | return self
320 | }
321 |
322 | public var circle: UIImage {
323 | var newImage: UIImage = self
324 | let sideLength = min(size.width, size.height)
325 | if size.width != size.height {
326 | let center = CGPoint(x: size.width * 0.5, y: size.height * 0.5)
327 | let newRect = CGRect(x: center.x - sideLength * 0.5, y: center.y - sideLength * 0.5, width: sideLength, height: sideLength)
328 | if let image = extracting(in: newRect) {
329 | newImage = image
330 | }
331 | }
332 | return newImage.remake(radius: sideLength * 0.5)
333 | }
334 |
335 | public func remake(alpha: CGFloat) -> UIImage {
336 | UIGraphicsBeginImageContext(size)
337 | defer { UIGraphicsEndImageContext() }
338 |
339 | let rect = CGRect(origin: CGPoint(x: 0, y: 0), size: size)
340 | draw(in: rect, blendMode: .normal, alpha: alpha)
341 | if let output = UIGraphicsGetImageFromCurrentImageContext() {
342 | return output
343 | }
344 | return self
345 | }
346 |
347 | public func rendering(color: UIColor, alpha: CGFloat = 1.0) -> UIImage {
348 | UIGraphicsBeginImageContext(size)
349 | defer { UIGraphicsEndImageContext() }
350 |
351 | color.setFill()
352 | let rect = CGRect(x: 0, y: 0, width: size.width, height: size.height)
353 | UIRectFill(rect)
354 | draw(in: rect, blendMode: .overlay, alpha: alpha)
355 | if let output = UIGraphicsGetImageFromCurrentImageContext() {
356 | return output
357 | }
358 | return self
359 | }
360 |
361 | public func builtThumbnail(targetSize: CGSize, useFitting: Bool = true) -> UIImage {
362 | UIGraphicsBeginImageContextWithOptions(targetSize, false, 0.0)
363 | defer { UIGraphicsEndImageContext() }
364 |
365 | // Establish the output thumbnail rectangle
366 | let targetRect = CGRectMake(origin: CGPoint.zero, size: targetSize)
367 | // Create the source image’s bounding rectangle
368 | let naturalRect = CGRectMake(origin: CGPoint.zero, size: size)
369 | // Calculate fitting or filling destination rectangle
370 | // See Chapter 2 for a discussion on these functions
371 | let destinationRect = useFitting ? naturalRect.fitting(in: targetRect) : naturalRect.filling(in: targetRect)
372 | // Draw the new thumbnail
373 | draw(in: destinationRect)
374 | // Retrieve and return the new image
375 | if let output = UIGraphicsGetImageFromCurrentImageContext() {
376 | return output
377 | }
378 | return self
379 | }
380 |
381 | /// Extract image
382 | public func extracting(in subRect: CGRect) -> UIImage? {
383 | if let imageRef = cgImage!.cropping(to: subRect) {
384 | return UIImage(cgImage: imageRef)
385 | }
386 | return nil
387 | }
388 |
389 | /// Watermarking
390 | public func watermarking(
391 | text: String,
392 | font: UIFont,
393 | color: UIColor = UIColor.white,
394 | rotate: Double = Double.pi / 4.0 ) -> UIImage
395 | {
396 | UIGraphicsBeginImageContextWithOptions(size, false, 0)
397 | defer { UIGraphicsEndImageContext() }
398 |
399 | let context = UIGraphicsGetCurrentContext()
400 |
401 | // Draw the original image into the context
402 | let targetRect = CGRectMake(size: size)
403 | draw(in: targetRect)
404 |
405 | // Rotate the context
406 | let center = targetRect.center
407 | context!.translateBy(x: center.x, y: center.y)
408 | context!.rotate(by: CGFloat(rotate))
409 | context!.translateBy(x: -center.x, y: -center.y)
410 |
411 | let stringSize = text.layoutSize(font: font, preferredMaxLayoutWidth: size.width)
412 | let stringRect = CGRectMake(size: stringSize).centering(in: CGRectMake(size: size))
413 |
414 | // Draw the string, using a blend mode
415 | context!.setBlendMode(.normal)
416 | (text as NSString).draw(
417 | in: stringRect,
418 | withAttributes: [NSAttributedStringKey.foregroundColor: color]
419 | )
420 |
421 | // Retrieve the new image
422 | if let output = UIGraphicsGetImageFromCurrentImageContext() {
423 | return output
424 | }
425 | return self
426 | }
427 | }
428 |
429 | public func UIImageFrom(
430 | color: UIColor,
431 | size: CGSize = CGSize(width: 1, height: 1),
432 | roundingCorners: UIRectCorner = .allCorners,
433 | radius: CGFloat = 0,
434 | strokeColor: UIColor = UIColor.clear,
435 | strokeLineWidth: CGFloat = 0) -> UIImage?
436 | {
437 | return UIImage.make(
438 | color: color,
439 | size: size,
440 | roundingCorners: roundingCorners,
441 | radius: radius,
442 | strokeColor: strokeColor,
443 | strokeLineWidth: strokeLineWidth
444 | )
445 | }
446 |
447 | public extension UIImage {
448 | @available(iOS 8.0, *)
449 | public var isQRCode: Bool {
450 | if let CIImage = CIImage(image: self) {
451 | let detector = CIDetector(
452 | ofType: CIDetectorTypeQRCode, context: nil,
453 | options: [CIDetectorAccuracy : CIDetectorAccuracyHigh]
454 | )
455 | let features = detector!.features(in: CIImage)
456 | if let first = features.first as? CIQRCodeFeature {
457 | return first.messageString!.count > 0
458 | }
459 | }
460 | return false
461 | }
462 | }
463 |
--------------------------------------------------------------------------------
/Sources/Extension/CoreGraphics+Extension.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CoreGraphics+Extension.swift
3 | // Copyright (c) 2015-2016 Red Rain (http://mochxiao.com).
4 | //
5 | // Permission is hereby granted, free of charge, to any person obtaining a copy
6 | // of this software and associated documentation files (the "Software"), to deal
7 | // in the Software without restriction, including without limitation the rights
8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | // copies of the Software, and to permit persons to whom the Software is
10 | // furnished to do so, subject to the following conditions:
11 | //
12 | // The above copyright notice and this permission notice shall be included in
13 | // all copies or substantial portions of the Software.
14 | //
15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | // THE SOFTWARE.
22 | //
23 |
24 | import Foundation
25 | import CoreGraphics
26 | import UIKit
27 |
28 | // MARK: -
29 |
30 | public extension Double {
31 | public var radian: CGFloat {
32 | return CGFloat(self / 180.0 * Double.pi)
33 | }
34 |
35 | public var angle: CGFloat {
36 | return CGFloat(self / Double.pi * 180.0)
37 | }
38 |
39 | public var pointToPixel: CGFloat {
40 | return CGFloat(self) * UIScreen.main.scale
41 | }
42 |
43 | public var pixelToPoint: CGFloat {
44 | return CGFloat(self) / UIScreen.main.scale
45 | }
46 | }
47 |
48 | public extension CGFloat {
49 | public var radian: CGFloat {
50 | return CGFloat(Double(native / 180.0) * Double.pi)
51 | }
52 |
53 | public var angle: CGFloat {
54 | return CGFloat(Double(native) / Double.pi * 180.0)
55 | }
56 |
57 | public var pointToPixel: CGFloat {
58 | return self * UIScreen.main.scale
59 | }
60 |
61 | public var pixelToPoint: CGFloat {
62 | return self / UIScreen.main.scale
63 | }
64 | }
65 |
66 | public extension CGFloat {
67 | public var ceilling: CGFloat { return ceil(self) }
68 | public var flooring: CGFloat { return floor(self) }
69 | }
70 |
71 | public extension CGPoint {
72 | public var ceilling: CGPoint { return CGPoint(x: ceil(x), y: ceil(y)) }
73 | public var flooring: CGPoint { return CGPoint(x: floor(x), y: floor(y)) }
74 |
75 | public func makeVector(to other: CGPoint) -> CGVector {
76 | return CGVector(dx: other.x - x, dy: y - other.y)
77 | }
78 | }
79 |
80 | public let CGPointZert = CGPoint.zero
81 |
82 | public func CGPointMake(_ x: CGFloat, _ y: CGFloat) -> CGPoint {
83 | return CGPoint(x: x, y: y)
84 | }
85 |
86 | public func CGPointMake(_ x: Double, _ y: Double) -> CGPoint {
87 | return CGPoint(x: x, y: y)
88 | }
89 |
90 | public func CGPointMake(_ x: Int, _ y: Int) -> CGPoint {
91 | return CGPoint(x: x, y: y)
92 | }
93 |
94 | // MARK: -
95 |
96 | public extension CGSize {
97 | public var ceilling: CGSize { return CGSize(width: ceil(width), height: ceil(height)) }
98 | public var flooring: CGSize { return CGSize(width: floor(width), height: floor(height)) }
99 |
100 | /// Multiply the size components by the factor
101 | public func scale(factor: CGFloat) -> CGSize {
102 | return CGSize(width: width * factor, height: height * factor)
103 | }
104 |
105 | /// Calculate scale for fitting a size to a destination size
106 | public func scale(aspectToFit size: CGSize) -> CGSize {
107 | return scale(factor: min(size.width / width, size.height / height))
108 | }
109 |
110 | // Calculate scale for filling a destination size
111 | public func scale(aspectToFill size: CGSize) -> CGSize {
112 | return scale(factor: max(size.width / width, size.height / height))
113 | }
114 | }
115 |
116 | public let CGSizeZert = CGSize.zero
117 |
118 | public func CGSizeMake(_ width: CGFloat, _ height: CGFloat) -> CGSize {
119 | return CGSize(width: width, height: height)
120 | }
121 |
122 | public func CGSizeMake(_ width: Double, _ height: Double) -> CGSize {
123 | return CGSize(width: width, height: height)
124 | }
125 |
126 | public func CGSizeMake(_ width: Int, _ height: Int) -> CGSize {
127 | return CGSize(width: width, height: height)
128 | }
129 |
130 | public func CGSizeEqualToSize(_ lhs: CGSize, _ rhs: CGSize) -> Bool {
131 | return lhs.equalTo(rhs)
132 | }
133 |
134 | // MARK: -
135 |
136 | public extension CGVector {
137 | public var ceilling: CGVector { return CGVector(dx: ceil(dx), dy: ceil(dy)) }
138 | public var flooring: CGVector { return CGVector(dx: floor(dx), dy: floor(dy)) }
139 | }
140 |
141 | public func CGVectorMake(_ dx: CGFloat, dy: CGFloat) -> CGVector {
142 | return CGVector(dx: dx, dy: dy)
143 | }
144 |
145 | public func CGVectorMake(_ dx: Double, dy: Double) -> CGVector {
146 | return CGVector(dx: dx, dy: dy)
147 | }
148 |
149 | public func CGVectorMake(_ dx: Int, dy: Int) -> CGVector {
150 | return CGVector(dx: dx, dy: dy)
151 | }
152 |
153 | // MARK: -
154 |
155 | public extension CGRect {
156 | public var ceilling: CGRect { return CGRectMake(size: size.ceilling) }
157 | public var flooring: CGRect { return CGRectMake(size: size.flooring) }
158 |
159 | /// Return a rect centered a source to a destination
160 | public func centering(in destination: CGRect) -> CGRect {
161 | let dx: CGFloat = destination.midX - midX
162 | let dy: CGFloat = destination.midY - midY
163 | return offsetBy(dx: dx, dy: dy)
164 | }
165 |
166 | /// Return a rect fitting a source to a destination
167 | public func fitting(in destination: CGRect) -> CGRect {
168 | let targetSize = size.scale(aspectToFit: destination.size)
169 | return CGRectMake(center: destination.center, size: targetSize)
170 | }
171 |
172 | /// Return a rect that fills the destination
173 | public func filling(in destination: CGRect) -> CGRect {
174 | let targetSize = size.scale(aspectToFill: destination.size)
175 | return CGRectMake(center: destination.center, size: targetSize)
176 | }
177 |
178 | public var left: CGFloat {
179 | set {
180 | var newOrigin = origin
181 | newOrigin.x = newValue
182 | origin = newOrigin
183 | }
184 | get { return origin.x }
185 | }
186 |
187 | public var centerX: CGFloat {
188 | set {
189 | let diff = newValue - midX
190 | var newOrigin = origin
191 | newOrigin.x += diff
192 | origin = newOrigin
193 | }
194 | get { return origin.x + size.width / 2.0 }
195 | }
196 |
197 | public var right: CGFloat {
198 | set {
199 | let diff = newValue - maxX
200 | var newOrigin = origin
201 | newOrigin.x += diff
202 | origin = newOrigin
203 | }
204 | get { return origin.x + size.width }
205 | }
206 |
207 | public var top: CGFloat {
208 | set {
209 | var newOrigin = origin
210 | newOrigin.y = newValue
211 | origin = newOrigin
212 | }
213 | get { return origin.y }
214 | }
215 |
216 | public var centerY: CGFloat {
217 | set {
218 | let diff = newValue - midY
219 | var newOrigin = origin
220 | newOrigin.y += diff
221 | origin = newOrigin
222 | }
223 | get { return origin.y + size.height / 2.0 }
224 | }
225 |
226 | public var bottom: CGFloat {
227 | set {
228 | let diff = newValue - maxY
229 | var newOrigin = origin
230 | newOrigin.y += diff
231 | origin = newOrigin
232 | }
233 | get { return origin.y + size.height }
234 | }
235 |
236 | public var center: CGPoint {
237 | set {
238 | var frame = self
239 | frame.origin.x = newValue.x - frame.size.width * 0.5
240 | frame.origin.y = newValue.y - frame.size.height * 0.5
241 | self = frame
242 | }
243 | get { return CGPoint(x: origin.x + size.width * 0.5, y: origin.y + size.height * 0.5) }
244 | }
245 |
246 | public var lengthX: CGFloat {
247 | set {
248 | var newSize = size
249 | newSize.width = newValue
250 | size = newSize
251 | }
252 | get { return size.width }
253 | }
254 |
255 | public var lengthY: CGFloat {
256 | set {
257 | var newSize = size
258 | newSize.height = newValue
259 | size = newSize
260 | }
261 | get { return size.height }
262 | }
263 | }
264 |
265 | public let CGRectZero = CGRect.zero
266 | public let CGRectNull = CGRect.null
267 | public let CGRectInfinite = CGRect.infinite
268 |
269 | /// Return center for rect
270 | public func CGRectGetCenter(_ rect: CGRect) -> CGPoint {
271 | return rect.center
272 | }
273 |
274 | public func CGRectMake(origin: CGPoint = CGPoint.zero, size: CGSize) -> CGRect {
275 | return CGRect(x: origin.x, y: origin.y, width: size.width, height: size.height)
276 | }
277 |
278 | public func CGRectMake(center: CGPoint, size: CGSize) -> CGRect {
279 | let halfWidth = size.width / 2.0
280 | let halfHeight = size.height / 2.0
281 | return CGRect(
282 | x: center.x - halfWidth,
283 | y: center.y - halfHeight,
284 | width: size.width,
285 | height: size.height
286 | )
287 | }
288 |
289 | public func CGRectMake(_ x: CGFloat, _ y: CGFloat, _ width: CGFloat, _ height: CGFloat) -> CGRect {
290 | return CGRect(x: x, y: y, width: width, height: height)
291 | }
292 |
293 | public func CGRectMake(_ x: Double, _ y: Double, _ width: Double, _ height: Double) -> CGRect {
294 | return CGRect(x: x, y: y, width: width, height: height)
295 | }
296 |
297 | public func CGRectMake(_ x: Int, _ y: Int, _ width: Int, _ height: Int) -> CGRect {
298 | return CGRect(x: x, y: y, width: width, height: height)
299 | }
300 |
301 | public func CGRectGetMinX(_ rect: CGRect) -> CGFloat {
302 | return rect.minX
303 | }
304 |
305 | public func CGRectGetMidX(_ rect: CGRect) -> CGFloat {
306 | return rect.midX
307 | }
308 |
309 | public func CGRectGetMaxX(_ rect: CGRect) -> CGFloat {
310 | return rect.maxX
311 | }
312 |
313 | public func CGRectGetMinY(_ rect: CGRect) -> CGFloat {
314 | return rect.minY
315 | }
316 |
317 | public func CGRectGetMidY(_ rect: CGRect) -> CGFloat {
318 | return rect.midY
319 | }
320 |
321 | public func CGRectGetMaxY(_ rect: CGRect) -> CGFloat {
322 | return rect.maxY
323 | }
324 |
325 | public func CGRectGetWidth(_ rect: CGRect) -> CGFloat {
326 | return rect.width
327 | }
328 |
329 | public func CGRectGetHeight(_ rect: CGRect) -> CGFloat {
330 | return rect.height
331 | }
332 |
333 | public func CGRectEqualToRect(_ lhs: CGRect, _ rhs: CGRect) -> Bool {
334 | return lhs.equalTo(rhs)
335 | }
336 |
337 | public func CGRectStandardize(_ rect: CGRect) -> CGRect {
338 | return CGRect(x: abs(rect.minX), y: abs(rect.minY), width: abs(rect.width), height: abs(rect.height))
339 | }
340 |
341 | public func CGRectIsEmpty(_ rect: CGRect) -> Bool {
342 | return rect.isEmpty
343 | }
344 |
345 | public func CGRectIsNull(_ rect: CGRect) -> Bool {
346 | return rect.isNull
347 | }
348 |
349 | public func CGRectIsInfinite(_ rect: CGRect) -> Bool {
350 | return rect.isInfinite
351 | }
352 |
353 | public func CGRectInset(_ rect: CGRect, _ dx: CGFloat, _ dy: CGFloat) -> CGRect {
354 | return rect.insetBy(dx: dx, dy: dy)
355 | }
356 |
357 | public func CGRectOffset(_ rect: CGRect, _ dx: CGFloat, _ dy: CGFloat) -> CGRect {
358 | return rect.offsetBy(dx: dx, dy: dy)
359 | }
360 |
361 | public func CGRectIntegral(_ rect: CGRect) -> CGRect {
362 | return rect.integral
363 | }
364 |
365 | public func CGRectUnion(_ rect1: CGRect, _ rect2: CGRect) -> CGRect {
366 | return rect1.union(rect2)
367 | }
368 |
369 | public func CGRectIntersection(_ rect1: CGRect, _ rect2: CGRect) -> CGRect {
370 | return rect2.intersection(rect2)
371 | }
372 |
373 | public func CGRectDivide(_ rect: CGRect, _ atDistance: CGFloat, _ from: CGRectEdge) -> (slice: CGRect, remainder: CGRect) {
374 | return rect.divided(atDistance: atDistance, from: from)
375 | }
376 |
377 | public func CGRectContainsPoint(_ rect: CGRect, _ point: CGPoint) -> Bool {
378 | return rect.contains(point)
379 | }
380 |
381 | public func CGRectContainsRect(_ rect1: CGRect, _ rect2: CGRect) -> Bool {
382 | return rect1.contains(rect2)
383 | }
384 |
385 | public func CGRectIntersectsRect(_ rect1: CGRect, _ rect2: CGRect) -> Bool {
386 | return rect1.intersects(rect2)
387 | }
388 |
389 | // MARK: -
390 |
391 | public extension CGAffineTransform {
392 | /// X scale from transform
393 | public var xScale: CGFloat { return sqrt(a * a + c * c) }
394 |
395 | /// Y scale from transform
396 | public var yScale: CGFloat { return sqrt(b * b + d * d) }
397 |
398 | /// Rotation in radians
399 | public var rotation: CGFloat { return CGFloat(atan2f(Float(b), Float(a))) }
400 | }
401 |
402 | // MARK: -
403 |
404 | public extension UIBezierPath {
405 | /// The path bounding box of `path'.
406 | public var boundingBox: CGRect {
407 | return cgPath.boundingBoxOfPath
408 | }
409 |
410 | /// The calculated bounds taking line width into account
411 | public var boundingWithLineBox: CGRect {
412 | return boundingBox.insetBy(dx: -lineWidth / 2.0, dy: -lineWidth / 2.0)
413 | }
414 |
415 | /// The center point for the path bounding box of `path'.
416 | public var boundingCenter: CGPoint {
417 | return CGRectGetCenter(boundingBox)
418 | }
419 |
420 | /// Translate path’s origin to its center before applying the transform
421 | public func centering(transform: CGAffineTransform) {
422 | let center = boundingCenter
423 | var t = CGAffineTransform.identity
424 | t = t.translatedBy(x: center.x, y: center.y)
425 | t = transform.concatenating(t)
426 | t = t.translatedBy(x: -center.x, y: -center.y)
427 | apply(t)
428 | }
429 |
430 | public func offset(vector: CGVector) {
431 | centering(transform: CGAffineTransform(translationX: vector.dx, y: vector.dy))
432 | }
433 |
434 | public func rotate(theta: CGFloat) {
435 | centering(transform: CGAffineTransform(rotationAngle: theta))
436 | }
437 |
438 | /// Move to a new center
439 | public func moveCenter(to position: CGPoint) {
440 | let bounds = boundingBox
441 | var vector = bounds.origin.makeVector(to: position)
442 | vector.dx -= bounds.size.width / 2.0
443 | vector.dy -= bounds.size.height / 2.0
444 | offset(vector: vector)
445 | }
446 |
447 | public func scale(xFactor: CGFloat, yFactor: CGFloat) {
448 | centering(transform: CGAffineTransform(scaleX: xFactor, y: yFactor))
449 | }
450 |
451 | public func fit(to destRect: CGRect) {
452 | let bounds = boundingBox
453 | let fitRect = bounds.filling(in: destRect)
454 | let factor = min(destRect.size.width / bounds.size.width, destRect.size.height / bounds.size.height)
455 | moveCenter(to: fitRect.center)
456 | scale(xFactor: factor, yFactor: factor)
457 | }
458 |
459 | public func horizontalInverst() {
460 | centering(transform: CGAffineTransform(scaleX: -1, y: 1))
461 | }
462 |
463 | public func verticalInverst() {
464 | centering(transform: CGAffineTransform(scaleX: 1, y: -1))
465 | }
466 |
467 | public class func make(string: NSString, font: UIFont) -> UIBezierPath? {
468 | // Initialize path
469 | let path = UIBezierPath()
470 | if (0 == string.length) {
471 | return path
472 | }
473 | // Create font ref
474 | let fontRef = CTFontCreateWithName((font.fontName as CFString?)!, font.pointSize, nil)
475 | // Create glyphs (that is, individual letter shapes)
476 |
477 |
478 | var characters = [UniChar]()
479 | let length = (string as NSString).length
480 | for i in stride(from: 0, to: length, by: 1) {
481 | characters.append((string as NSString).character(at: i))
482 | }
483 |
484 | let glyphs = UnsafeMutablePointer.allocate(capacity: length)
485 | glyphs.initialize(to: 0)
486 |
487 | let success = CTFontGetGlyphsForCharacters(font, characters, glyphs, length)
488 | if (!success) {
489 | logging("Error retrieving string glyphs")
490 | free(glyphs)
491 | return nil
492 | }
493 |
494 | // Draw each char into path
495 | for i in 0 ..< string.length {
496 | // Glyph to CGPath
497 | let glyph = glyphs[i]
498 | guard let pathRef = CTFontCreatePathForGlyph(fontRef, glyph, nil) else {
499 | return nil
500 | }
501 |
502 | // Append CGPath
503 | path.append(UIBezierPath(cgPath: pathRef))
504 |
505 | // Offset by size
506 | let size = (string.substring(with: NSRange( i ..< i + 1)) as NSString).size(withAttributes: [NSAttributedStringKey.font: font])
507 | path.offset(vector: CGVector(dx: -size.width, dy: 0))
508 | }
509 |
510 | // Clean up
511 | free(glyphs)
512 |
513 | // Return the path to the UIKit coordinate system MirrorPathVertically(path);
514 | return path
515 | }
516 |
517 | public class func makePolygon(numberOfSides: Int) -> UIBezierPath? {
518 | if numberOfSides < 3 {
519 | logging("Error: Please supply at least 3 sides")
520 | return nil
521 | }
522 |
523 | let path = UIBezierPath()
524 |
525 | // Use a unit rectangle as the destination
526 | let destinationRect = CGRect(x: 0, y: 0, width: 1, height: 1)
527 | let center = CGRectGetCenter(destinationRect)
528 | let radius: CGFloat = 0.5
529 |
530 | var firstPoint = true
531 | for i in 0 ..< numberOfSides - 1 {
532 | let theta: Double = Double.pi + Double(i) * 2 * Double.pi / Double(numberOfSides)
533 | let dTheta: Double = 2 * Double.pi / Double(numberOfSides)
534 | var point = CGPoint.zero
535 | if firstPoint {
536 | point.x = center.x + radius * CGFloat(sin(theta))
537 | point.y = center.y + radius * CGFloat(cos(dTheta))
538 | firstPoint = false
539 | }
540 | point.x = center.x + radius * CGFloat(sin(theta + dTheta))
541 | point.y = center.y + radius * CGFloat(cos(theta + dTheta))
542 |
543 | path.addLine(to: point)
544 | }
545 | path.close()
546 |
547 | return path
548 | }
549 | }
550 |
551 | /// Query context for size and use screen scale to map from Quartz pixels to UIKit points
552 | public func UIKitGetContextSize() -> CGSize {
553 | guard let context = UIGraphicsGetCurrentContext() else {
554 | return CGSize.zero
555 | }
556 |
557 | let size = CGSize(
558 | width: CGFloat(context.width),
559 | height: CGFloat(context.height)
560 | )
561 | let scale: CGFloat = UIScreen.main.scale
562 | return CGSize(
563 | width: size.width / scale,
564 | height: size.height / scale
565 | )
566 | }
567 |
568 | public extension CGContext {
569 | /// Horizontal flip context by supplying the size
570 | public func horizontalInverst(size: CGSize) {
571 | textMatrix = CGAffineTransform.identity
572 | translateBy(x: size.width, y: 0)
573 | scaleBy(x: -1.0, y: 1.0)
574 | }
575 |
576 | /// Vertical flip context by supplying the size
577 | public func verticalInverst(size: CGSize) {
578 | textMatrix = CGAffineTransform.identity
579 | translateBy(x: 0, y: size.height)
580 | scaleBy(x: 1.0, y: -1.0)
581 | }
582 |
583 | /// Flip context by retrieving image
584 | @discardableResult
585 | public func horizontalInverstImage() -> Bool {
586 | if let image = UIGraphicsGetImageFromCurrentImageContext() {
587 | horizontalInverst(size: image.size)
588 | return true
589 | }
590 | return false
591 | }
592 |
593 | /// Flip context by retrieving image
594 | @discardableResult
595 | public func verticalInverstImage() -> Bool {
596 | if let image = UIGraphicsGetImageFromCurrentImageContext() {
597 | verticalInverst(size: image.size)
598 | return true
599 | }
600 | return false
601 | }
602 | }
603 |
--------------------------------------------------------------------------------