├── _config.yml
├── .gitignore
├── assets
├── exampleView_01.png
└── SwiftUIKit_logo_v1.png
├── documentation
├── BarButton.md
├── _Footer.md
├── AttributedString.md
├── StringAttributes.md
├── AttributedStringKey.md
├── TableHeaderFooterViewHandler.md
├── TableDidSelectIndexPathHandler.md
├── TableHighlightIndexPathHandler.md
├── TableViewCell.md
├── CollectionViewCell.md
├── DataIdentifiable.md
├── CellDisplayable.md
├── CellUpdatable.md
├── CellConfigurable.md
├── Navigate_NavigationStyle.md
├── ScrollView.md
├── Spacer.md
├── HScroll.md
├── VScroll.md
├── Switch.md
├── Divider.md
├── ContainerView.md
├── EffectView.md
├── Slider.md
├── BlurView.md
├── Padding.md
├── ViewController.md
├── SafeAreaView.md
├── VibrancyView.md
├── ObservedView.md
├── MapPoint.md
├── Image.md
├── Navigate_ToastStyle.md
├── NavButton.md
├── LoadingView.md
├── Button.md
├── ZStack.md
├── LoadingImage.md
├── TextField.md
├── MultiLineField.md
├── List.md
├── HStack.md
├── VStack.md
├── _Sidebar.md
├── WebView.md
├── Home.md
├── Label.md
├── Navigate.md
├── Map.md
└── TableView.md
├── Sources
└── SwiftUIKit
│ ├── Extensions
│ ├── SwiftFu+.swift
│ ├── UIImage+SwiftUIKit.swift
│ ├── UIButton+SwiftUIKit.swift
│ ├── UILabel+SwiftUIKit.swift
│ ├── UIImageView+SwiftUIKit.swift
│ ├── UITextField+SwiftUIKit.swift
│ ├── UIBarButton+SwiftUIKit.swift
│ ├── UIAlertAction+SwiftUIKit.swift
│ ├── UIAppearance+SwiftUIKit.swift
│ ├── UISlider+SwiftUIKit.swift
│ ├── UISwitch+SwiftUIKit.swift
│ ├── UITextView+SwiftUIKit.swift
│ ├── UIControl+SwiftUIKit.swift
│ ├── NSLayoutConstraint+SwiftUIKit.swift
│ ├── NSMutableAttributedString+SwiftUIKit.swift
│ ├── UIViewController+SwiftUIKit.swift
│ ├── UIView+Chain.swift
│ └── CALayer+SwiftUIKit.swift
│ ├── Views
│ ├── ScrollView.swift
│ ├── Divider.swift
│ ├── Switch.swift
│ ├── Slider.swift
│ ├── Spacer.swift
│ ├── ContainerView.swift
│ ├── LoadingView.swift
│ ├── ObservedView.swift
│ ├── EffectView.swift
│ ├── NavButton.swift
│ ├── Button.swift
│ ├── MultiLineField.swift
│ ├── Image.swift
│ ├── TextField.swift
│ ├── WebView.swift
│ ├── List.swift
│ ├── Label.swift
│ └── LoadingImage.swift
│ ├── Containers
│ ├── HScroll.swift
│ ├── VScroll.swift
│ ├── SafeAreaView.swift
│ ├── ZStack.swift
│ ├── HStack.swift
│ └── VStack.swift
│ └── Navigation
│ └── Navigate.swift
├── .swiftpm
└── xcode
│ └── package.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ └── IDEWorkspaceChecks.plist
├── CONTRIBUTING.md
├── .github
├── ISSUE_TEMPLATE
│ ├── feature_request.md
│ └── bug_report.md
├── FUNDING.yml
└── workflows
│ └── swift.yml
├── LICENSE
├── Package.swift
├── Tests
└── SwiftUIKitTests
│ ├── ADA
│ └── BasicADATests.swift
│ ├── ContainerView
│ └── ContainerViewTests.swift
│ ├── NSAttributedString
│ └── NSMutableAttributedStringTests.swift
│ ├── TableView
│ └── TableViewTests.swift
│ ├── Case
│ └── Extensions
│ │ └── CALayer+SwiftUIKitTests.swift
│ └── Core
│ └── BasicSwiftUIKitTests.swift
├── CODE_OF_CONDUCT.md
└── README.md
/_config.yml:
--------------------------------------------------------------------------------
1 | theme: jekyll-theme-cayman
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | /.build
3 | /Packages
4 | /*.xcodeproj
5 | xcuserdata/
6 | build
7 | *.resolved
--------------------------------------------------------------------------------
/assets/exampleView_01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xLet/SwiftUIKit/HEAD/assets/exampleView_01.png
--------------------------------------------------------------------------------
/assets/SwiftUIKit_logo_v1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xLet/SwiftUIKit/HEAD/assets/SwiftUIKit_logo_v1.png
--------------------------------------------------------------------------------
/documentation/BarButton.md:
--------------------------------------------------------------------------------
1 | # BarButton
2 |
3 | ``` swift
4 | public typealias BarButton = UIBarButtonItem
5 | ```
6 |
--------------------------------------------------------------------------------
/documentation/_Footer.md:
--------------------------------------------------------------------------------
1 | Generated at 2021-03-16T21:29:45-0500 using [swift-doc](https://github.com/SwiftDocOrg/swift-doc) 1.0.0-beta.5.
2 |
--------------------------------------------------------------------------------
/documentation/AttributedString.md:
--------------------------------------------------------------------------------
1 | # AttributedString
2 |
3 | ``` swift
4 | public typealias AttributedString = NSMutableAttributedString
5 | ```
6 |
--------------------------------------------------------------------------------
/documentation/StringAttributes.md:
--------------------------------------------------------------------------------
1 | # StringAttributes
2 |
3 | ``` swift
4 | public typealias StringAttributes = [AttributedStringKey: Any]
5 | ```
6 |
--------------------------------------------------------------------------------
/Sources/SwiftUIKit/Extensions/SwiftFu+.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SwiftFu+.swift
3 | //
4 | //
5 | // Created by Leif on 3/16/21.
6 | //
7 |
8 | import SwiftFu
9 |
--------------------------------------------------------------------------------
/documentation/AttributedStringKey.md:
--------------------------------------------------------------------------------
1 | # AttributedStringKey
2 |
3 | ``` swift
4 | public typealias AttributedStringKey = NSAttributedString.Key
5 | ```
6 |
--------------------------------------------------------------------------------
/documentation/TableHeaderFooterViewHandler.md:
--------------------------------------------------------------------------------
1 | # TableHeaderFooterViewHandler
2 |
3 | ``` swift
4 | public typealias TableHeaderFooterViewHandler = (Int) -> UIView?
5 | ```
6 |
--------------------------------------------------------------------------------
/documentation/TableDidSelectIndexPathHandler.md:
--------------------------------------------------------------------------------
1 | # TableDidSelectIndexPathHandler
2 |
3 | ``` swift
4 | public typealias TableDidSelectIndexPathHandler = (IndexPath) -> Void
5 | ```
6 |
--------------------------------------------------------------------------------
/documentation/TableHighlightIndexPathHandler.md:
--------------------------------------------------------------------------------
1 | # TableHighlightIndexPathHandler
2 |
3 | ``` swift
4 | public typealias TableHighlightIndexPathHandler = (IndexPath) -> Bool
5 | ```
6 |
--------------------------------------------------------------------------------
/documentation/TableViewCell.md:
--------------------------------------------------------------------------------
1 | # TableViewCell
2 |
3 | ``` swift
4 | @available(iOS 11.0, *) public typealias TableViewCell = DataIdentifiable & CellConfigurable & CellUpdatable & UITableViewCell
5 | ```
6 |
--------------------------------------------------------------------------------
/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/documentation/CollectionViewCell.md:
--------------------------------------------------------------------------------
1 | # CollectionViewCell
2 |
3 | ``` swift
4 | @available(iOS 11.0, *) public typealias CollectionViewCell = DataIdentifiable & CellConfigurable & CellUpdatable & UICollectionViewCell
5 | ```
6 |
--------------------------------------------------------------------------------
/documentation/DataIdentifiable.md:
--------------------------------------------------------------------------------
1 | # DataIdentifiable
2 |
3 | ``` swift
4 | @available(iOS 11.0, *) public protocol DataIdentifiable
5 | ```
6 |
7 | ## Requirements
8 |
9 | ### ID
10 |
11 | ``` swift
12 | var ID: String
13 | ```
14 |
--------------------------------------------------------------------------------
/documentation/CellDisplayable.md:
--------------------------------------------------------------------------------
1 | # CellDisplayable
2 |
3 | ``` swift
4 | @available(iOS 11.0, *) public protocol CellDisplayable
5 | ```
6 |
7 | ## Requirements
8 |
9 | ### cellID
10 |
11 | ``` swift
12 | var cellID: String
13 | ```
14 |
--------------------------------------------------------------------------------
/documentation/CellUpdatable.md:
--------------------------------------------------------------------------------
1 | # CellUpdatable
2 |
3 | ``` swift
4 | @available(iOS 11.0, *) public protocol CellUpdatable
5 | ```
6 |
7 | ## Requirements
8 |
9 | ### update(forData:)
10 |
11 | ``` swift
12 | func update(forData data: CellDisplayable)
13 | ```
14 |
--------------------------------------------------------------------------------
/documentation/CellConfigurable.md:
--------------------------------------------------------------------------------
1 | # CellConfigurable
2 |
3 | ``` swift
4 | @available(iOS 11.0, *) public protocol CellConfigurable
5 | ```
6 |
7 | ## Requirements
8 |
9 | ### configure(forData:)
10 |
11 | ``` swift
12 | func configure(forData data: CellDisplayable)
13 | ```
14 |
--------------------------------------------------------------------------------
/documentation/Navigate_NavigationStyle.md:
--------------------------------------------------------------------------------
1 | # Navigate.NavigationStyle
2 |
3 | ``` swift
4 | public enum NavigationStyle
5 | ```
6 |
7 | ## Enumeration Cases
8 |
9 | ### `push`
10 |
11 | ``` swift
12 | case push
13 | ```
14 |
15 | ### `modal`
16 |
17 | ``` swift
18 | case modal
19 | ```
20 |
--------------------------------------------------------------------------------
/documentation/ScrollView.md:
--------------------------------------------------------------------------------
1 | # ScrollView
2 |
3 | ``` swift
4 | @available(iOS 9.0, *) public class ScrollView: UIScrollView
5 | ```
6 |
7 | ## Inheritance
8 |
9 | `UIScrollView`
10 |
11 | ## Initializers
12 |
13 | ### `init(_:)`
14 |
15 | ``` swift
16 | public init(_ closure: (() -> UIView)? = nil)
17 | ```
18 |
--------------------------------------------------------------------------------
/documentation/Spacer.md:
--------------------------------------------------------------------------------
1 | # Spacer
2 |
3 | ``` swift
4 | @available(iOS 9.0, *) public class Spacer: UIView
5 | ```
6 |
7 | ## Inheritance
8 |
9 | `UIView`
10 |
11 | ## Initializers
12 |
13 | ### `init(height:width:)`
14 |
15 | ``` swift
16 | public init(height: Float? = nil, width: Float? = nil)
17 | ```
18 |
--------------------------------------------------------------------------------
/.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/documentation/HScroll.md:
--------------------------------------------------------------------------------
1 | # HScroll
2 |
3 | ``` swift
4 | @available(iOS 9.0, *) public class HScroll: UIView
5 | ```
6 |
7 | ## Inheritance
8 |
9 | `UIView`
10 |
11 | ## Initializers
12 |
13 | ### `init(withPadding:_:)`
14 |
15 | ``` swift
16 | public init(withPadding padding: Float = 0, _ closure: () -> UIView)
17 | ```
18 |
--------------------------------------------------------------------------------
/documentation/VScroll.md:
--------------------------------------------------------------------------------
1 | # VScroll
2 |
3 | ``` swift
4 | @available(iOS 9.0, *) public class VScroll: UIView
5 | ```
6 |
7 | ## Inheritance
8 |
9 | `UIView`
10 |
11 | ## Initializers
12 |
13 | ### `init(withPadding:_:)`
14 |
15 | ``` swift
16 | public init(withPadding padding: Float = 0, _ closure: () -> UIView)
17 | ```
18 |
--------------------------------------------------------------------------------
/documentation/Switch.md:
--------------------------------------------------------------------------------
1 | # Switch
2 |
3 | ``` swift
4 | @available(iOS 9.0, *) public class Switch: UISwitch
5 | ```
6 |
7 | ## Inheritance
8 |
9 | `UISwitch`
10 |
11 | ## Initializers
12 |
13 | ### `init(isOn:toggleChanged:)`
14 |
15 | ``` swift
16 | public init(isOn: Bool = false, toggleChanged: ((Bool) -> Void)? = nil)
17 | ```
18 |
--------------------------------------------------------------------------------
/documentation/Divider.md:
--------------------------------------------------------------------------------
1 | # Divider
2 |
3 | ``` swift
4 | @available(iOS 9.0, *) public class Divider: UIView
5 | ```
6 |
7 | ## Inheritance
8 |
9 | `UIView`
10 |
11 | ## Initializers
12 |
13 | ### `init(_:color:)`
14 |
15 | ``` swift
16 | public init(_ axis: NSLayoutConstraint.Axis = .horizontal, color: UIColor? = .systemGray)
17 | ```
18 |
--------------------------------------------------------------------------------
/documentation/ContainerView.md:
--------------------------------------------------------------------------------
1 | # ContainerView
2 |
3 | ``` swift
4 | @available(iOS 9.0, *) public class ContainerView: UIView
5 | ```
6 |
7 | ## Inheritance
8 |
9 | `UIView`
10 |
11 | ## Initializers
12 |
13 | ### `init(parent:child:)`
14 |
15 | ``` swift
16 | public init(parent: UIViewController, child: () -> UIViewController)
17 | ```
18 |
--------------------------------------------------------------------------------
/documentation/EffectView.md:
--------------------------------------------------------------------------------
1 | # EffectView
2 |
3 | ``` swift
4 | @available(iOS 9.0, *) public class EffectView: UIVisualEffectView
5 | ```
6 |
7 | ## Inheritance
8 |
9 | `UIVisualEffectView`
10 |
11 | ## Initializers
12 |
13 | ### `init(for:closure:)`
14 |
15 | ``` swift
16 | public init(for effect: UIVisualEffect? = nil, closure: () -> UIView)
17 | ```
18 |
--------------------------------------------------------------------------------
/documentation/Slider.md:
--------------------------------------------------------------------------------
1 | # Slider
2 |
3 | ``` swift
4 | @available(iOS 9.0, *) public class Slider: UISlider
5 | ```
6 |
7 | ## Inheritance
8 |
9 | `UISlider`
10 |
11 | ## Initializers
12 |
13 | ### `init(value:from:to:valueChangedHandler:)`
14 |
15 | ``` swift
16 | public init(value: Float, from: Float, to: Float, valueChangedHandler: ((Float) -> Void)? = nil)
17 | ```
18 |
--------------------------------------------------------------------------------
/documentation/BlurView.md:
--------------------------------------------------------------------------------
1 | # BlurView
2 |
3 | ``` swift
4 | @available(iOS 10.0, *) public class BlurView: UIVisualEffectView
5 | ```
6 |
7 | ## Inheritance
8 |
9 | `UIVisualEffectView`
10 |
11 | ## Initializers
12 |
13 | ### `init(style:closure:)`
14 |
15 | ``` swift
16 | public init(style blur: UIBlurEffect.Style = UIBlurEffect.Style.regular, closure: () -> UIView)
17 | ```
18 |
--------------------------------------------------------------------------------
/documentation/Padding.md:
--------------------------------------------------------------------------------
1 | # Padding
2 |
3 | ``` swift
4 | public enum Padding
5 | ```
6 |
7 | ## Enumeration Cases
8 |
9 | ### `leading`
10 |
11 | ``` swift
12 | case leading(: Float)
13 | ```
14 |
15 | ### `trailing`
16 |
17 | ``` swift
18 | case trailing(: Float)
19 | ```
20 |
21 | ### `top`
22 |
23 | ``` swift
24 | case top(: Float)
25 | ```
26 |
27 | ### `bottom`
28 |
29 | ``` swift
30 | case bottom(: Float)
31 | ```
32 |
--------------------------------------------------------------------------------
/Sources/SwiftUIKit/Extensions/UIImage+SwiftUIKit.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIImage+SwiftUIKit.swift
3 | // SwiftUIKit
4 | //
5 | // Created by Zach Eriksen on 7/7/20.
6 | //
7 |
8 | import UIKit
9 |
10 | public extension UIImage {
11 | var template: UIImage {
12 | guard renderingMode != .alwaysTemplate else {
13 | return self
14 | }
15 |
16 | return withRenderingMode(.alwaysTemplate)
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/documentation/ViewController.md:
--------------------------------------------------------------------------------
1 | # ViewController
2 |
3 | ``` swift
4 | @available(iOS 9.0, *) public class ViewController: UIViewController
5 | ```
6 |
7 | ## Inheritance
8 |
9 | `UIViewController`
10 |
11 | ## Initializers
12 |
13 | ### `init(_:)`
14 |
15 | ``` swift
16 | public init(_ closure: @escaping (() -> UIView))
17 | ```
18 |
19 | ## Methods
20 |
21 | ### `viewDidLoad()`
22 |
23 | ``` swift
24 | public override func viewDidLoad()
25 | ```
26 |
--------------------------------------------------------------------------------
/documentation/SafeAreaView.md:
--------------------------------------------------------------------------------
1 | # SafeAreaView
2 |
3 | A View that respects the SafeArea
4 |
5 | ``` swift
6 | @available(iOS 11.0, *) public class SafeAreaView: UIView
7 | ```
8 |
9 | ## Inheritance
10 |
11 | `UIView`
12 |
13 | ## Initializers
14 |
15 | ### `init(_:)`
16 |
17 | Create a SafeAreaView
18 |
19 | ``` swift
20 | public init(_ closure: () -> UIView)
21 | ```
22 |
23 | #### Parameters
24 |
25 | - closure: A trailing closure that accepts a view
26 |
--------------------------------------------------------------------------------
/documentation/VibrancyView.md:
--------------------------------------------------------------------------------
1 | # VibrancyView
2 |
3 | ``` swift
4 | @available(iOS 13.0, *) public class VibrancyView: UIVisualEffectView
5 | ```
6 |
7 | ## Inheritance
8 |
9 | `UIVisualEffectView`
10 |
11 | ## Initializers
12 |
13 | ### `init(blurStyle:vibrancyStyle:closure:)`
14 |
15 | ``` swift
16 | public init(blurStyle blur: UIBlurEffect.Style = UIBlurEffect.Style.regular, vibrancyStyle vibrancy: UIVibrancyEffectStyle = .fill, closure: () -> UIView)
17 | ```
18 |
--------------------------------------------------------------------------------
/Sources/SwiftUIKit/Extensions/UIButton+SwiftUIKit.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIButton+SwiftUIKit.swift
3 | // SwiftUIKit
4 | //
5 | // Created by Zach Eriksen on 7/7/20.
6 | //
7 |
8 | import UIKit
9 |
10 | public extension UIButton {
11 | @discardableResult
12 | func set(titleColor: UIColor?,
13 | forState state: UIControl.State = .normal) -> Self {
14 | setTitleColor(titleColor, for: state)
15 |
16 | return self
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/Sources/SwiftUIKit/Views/ScrollView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ScrollView.swift
3 | //
4 | //
5 | // Created by Zach Eriksen on 11/4/19.
6 | //
7 |
8 | import UIKit
9 |
10 | @available(iOS 9.0, *)
11 | public class ScrollView: UIScrollView {
12 | public init(_ closure: (() -> UIView)? = nil) {
13 | super.init(frame: .zero)
14 |
15 | _ = closure.map { embed($0) }
16 | }
17 |
18 | required init?(coder: NSCoder) {
19 | fatalError("init(coder:) has not been implemented")
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/Sources/SwiftUIKit/Extensions/UILabel+SwiftUIKit.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UILabel+SwiftUIKit.swift
3 | // SwiftUIKit
4 | //
5 | // Created by Zach Eriksen on 7/7/20.
6 | //
7 |
8 | import UIKit
9 |
10 | public extension UILabel {
11 | @discardableResult
12 | func text(color: UIColor?) -> Self {
13 | textColor = color
14 |
15 | return self
16 | }
17 |
18 | @discardableResult
19 | func text(_ value: String?) -> Self {
20 | text = value
21 |
22 | return self
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/Sources/SwiftUIKit/Extensions/UIImageView+SwiftUIKit.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIImageView+SwiftUIKit.swift
3 | // SwiftUIKit
4 | //
5 | // Created by Zach Eriksen on 7/7/20.
6 | //
7 |
8 | import UIKit
9 |
10 | public extension UIImageView {
11 | @discardableResult
12 | func templateImage() -> Self {
13 | image = image?.template
14 |
15 | return self
16 | }
17 |
18 | @discardableResult
19 | func tint(color: UIColor?) -> Self {
20 | tintColor = color
21 |
22 | return self
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/Sources/SwiftUIKit/Extensions/UITextField+SwiftUIKit.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UITextField+SwiftUIKit.swift
3 | // SwiftUIKit
4 | //
5 | // Created by Zach Eriksen on 7/7/20.
6 | //
7 |
8 | import UIKit
9 |
10 | public extension UITextField {
11 | @discardableResult
12 | func text(color: UIColor?) -> Self {
13 | textColor = color
14 |
15 | return self
16 | }
17 |
18 | @discardableResult
19 | func text(_ value: String?) -> Self {
20 | text = value
21 |
22 | return self
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/Sources/SwiftUIKit/Extensions/UIBarButton+SwiftUIKit.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIBarButton+SwiftUIKit.swift
3 | //
4 | //
5 | // Created by Zach Eriksen on 10/30/19.
6 | //
7 |
8 | import UIKit
9 |
10 | public typealias BarButton = UIBarButtonItem
11 |
12 | public extension UIBarButtonItem {
13 |
14 | /// Create a UIBarButtonItem with a CustomView
15 | /// - Parameters:
16 | /// - closure: A trailing closure that accepts a view
17 | convenience init(customView: () -> UIView) {
18 | self.init(customView: customView())
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | When contributing to this repository, please first discuss the change you wish to make via issue,
4 | email, or any other method with the owners of this repository before making a change.
5 |
6 | Please note we have a code of conduct, please follow it in all your interactions with the project.
7 |
8 | ## Pull Request Process
9 |
10 | **You may merge the Pull Request in once you have the sign-off of two other developers, or if you
11 | do not have permission to do that, you may request the second reviewer to merge it for you.**
12 |
--------------------------------------------------------------------------------
/documentation/ObservedView.md:
--------------------------------------------------------------------------------
1 | # ObservedView
2 |
3 | ``` swift
4 | @available(iOS 9.0, *) public class ObservedView: UIView where View: UIView
5 | ```
6 |
7 | ## Inheritance
8 |
9 | `UIView`
10 |
11 | ## Initializers
12 |
13 | ### `init(view:initialValue:onChangeHandler:)`
14 |
15 | ``` swift
16 | public init(view: View, initialValue: Value?, onChangeHandler: @escaping (_ newValue: Value?, _ view: View) -> Void)
17 | ```
18 |
19 | ## Methods
20 |
21 | ### `update(value:)`
22 |
23 | ``` swift
24 | @discardableResult public func update(value: Value) -> Variable
25 | ```
26 |
--------------------------------------------------------------------------------
/Sources/SwiftUIKit/Extensions/UIAlertAction+SwiftUIKit.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIAlertAction+SwiftUIKit.swift
3 | //
4 | //
5 | // Created by Zach Eriksen on 12/1/19.
6 | //
7 |
8 | import UIKit
9 |
10 | public extension UIAlertAction {
11 | /// Quick Cancel UIAlertAction
12 | class var cancel: UIAlertAction {
13 | return UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
14 | }
15 |
16 | /// Quick Dismiss UIAlertAction
17 | class var dismiss: UIAlertAction {
18 | return UIAlertAction(title: "Dismiss", style: .cancel, handler: nil)
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/Sources/SwiftUIKit/Extensions/UIAppearance+SwiftUIKit.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIAppearance+SwiftUIKit.swift
3 | //
4 | //
5 | // Created by Zach Eriksen on 8/25/20.
6 | //
7 |
8 | import UIKit
9 |
10 | @available(iOS 9.0, *)
11 | public extension UIAppearance where Self: UIView {
12 | func observed(
13 | initialValue: Value,
14 | onChangeHandler: @escaping (_ newValue: Value?, _ view: Self) -> Void
15 | ) -> ObservedView {
16 | ObservedView(view: self,
17 | initialValue: initialValue,
18 | onChangeHandler: onChangeHandler)
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/documentation/MapPoint.md:
--------------------------------------------------------------------------------
1 | # MapPoint
2 |
3 | ``` swift
4 | public struct MapPoint
5 | ```
6 |
7 | ## Initializers
8 |
9 | ### `init(latitude:longitude:title:subtitle:)`
10 |
11 | ``` swift
12 | public init(latitude: Double, longitude: Double, title: String, subtitle: String)
13 | ```
14 |
15 | ## Properties
16 |
17 | ### `latitude`
18 |
19 | ``` swift
20 | let latitude: Double
21 | ```
22 |
23 | ### `longitude`
24 |
25 | ``` swift
26 | let longitude: Double
27 | ```
28 |
29 | ### `title`
30 |
31 | ``` swift
32 | let title: String
33 | ```
34 |
35 | ### `subtitle`
36 |
37 | ``` swift
38 | let subtitle: String
39 | ```
40 |
--------------------------------------------------------------------------------
/documentation/Image.md:
--------------------------------------------------------------------------------
1 | # Image
2 |
3 | ``` swift
4 | @available(iOS 9.0, *) public class Image: UIImageView
5 | ```
6 |
7 | ## Inheritance
8 |
9 | `UIImageView`
10 |
11 | ## Initializers
12 |
13 | ### `init(_:)`
14 |
15 | ``` swift
16 | public init(_ image: UIImage)
17 | ```
18 |
19 | ### `init(color:)`
20 |
21 | ``` swift
22 | public init(color: UIColor)
23 | ```
24 |
25 | ### `init(named:)`
26 |
27 | ``` swift
28 | public init(named name: String)
29 | ```
30 |
31 | ## Methods
32 |
33 | ### `contentMode(_:)`
34 |
35 | ``` swift
36 | @discardableResult public func contentMode(_ mode: UIView.ContentMode) -> Self
37 | ```
38 |
--------------------------------------------------------------------------------
/Sources/SwiftUIKit/Extensions/UISlider+SwiftUIKit.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UISlider+SwiftUIKit.swift
3 | // SwiftUIKit
4 | //
5 | // Created by Zach Eriksen on 7/7/20.
6 | //
7 |
8 | import UIKit
9 |
10 | public extension UISlider {
11 | @discardableResult
12 | func set(thumbImage: UIImage?,
13 | forState state: UIControl.State = .normal) -> Self {
14 | setThumbImage(thumbImage, for: state)
15 |
16 | return self
17 | }
18 |
19 | @discardableResult
20 | func tint(thumbColor: UIColor?) -> Self {
21 | thumbTintColor = thumbColor
22 |
23 | return self
24 | }
25 | }
26 |
27 |
--------------------------------------------------------------------------------
/Sources/SwiftUIKit/Extensions/UISwitch+SwiftUIKit.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UISwitch+SwiftUIKit.swift
3 | // SwiftUIKit
4 | //
5 | // Created by Zach Eriksen on 7/7/20.
6 | //
7 |
8 | import UIKit
9 |
10 | public extension UISwitch {
11 | @discardableResult
12 | func turn(on: Bool) -> Self {
13 | DispatchQueue.main.async {
14 | self.isOn = on
15 | }
16 |
17 | return self
18 | }
19 |
20 | @discardableResult
21 | func turnOn() -> Self {
22 | turn(on: true)
23 | }
24 |
25 | @discardableResult
26 | func turnOff() -> Self {
27 | turn(on: false)
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/documentation/Navigate_ToastStyle.md:
--------------------------------------------------------------------------------
1 | # Navigate.ToastStyle
2 |
3 | ``` swift
4 | public enum ToastStyle
5 | ```
6 |
7 | ## Enumeration Cases
8 |
9 | ### `error`
10 |
11 | ``` swift
12 | case error
13 | ```
14 |
15 | ### `warning`
16 |
17 | ``` swift
18 | case warning
19 | ```
20 |
21 | ### `success`
22 |
23 | ``` swift
24 | case success
25 | ```
26 |
27 | ### `info`
28 |
29 | ``` swift
30 | case info
31 | ```
32 |
33 | ### `debug`
34 |
35 | ``` swift
36 | case debug
37 | ```
38 |
39 | ### `custom`
40 |
41 | ``` swift
42 | case custom
43 | ```
44 |
45 | ## Properties
46 |
47 | ### `color`
48 |
49 | ``` swift
50 | var color: UIColor
51 | ```
52 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Is your feature request related to a problem? Please describe.**
11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12 |
13 | **Describe the solution you'd like**
14 | A clear and concise description of what you want to happen.
15 |
16 | **Describe alternatives you've considered**
17 | A clear and concise description of any alternative solutions or features you've considered.
18 |
19 | **Additional context**
20 | Add any other context or screenshots about the feature request here.
21 |
--------------------------------------------------------------------------------
/Sources/SwiftUIKit/Views/Divider.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Divider.swift
3 | //
4 | //
5 | // Created by Zach Eriksen on 11/2/19.
6 | //
7 |
8 | import UIKit
9 |
10 | @available(iOS 9.0, *)
11 | public class Divider: UIView {
12 | public init(
13 | _ axis: NSLayoutConstraint.Axis = .horizontal,
14 | color: UIColor? = .systemGray
15 | ) {
16 | super.init(frame: .zero)
17 |
18 | self.backgroundColor = color
19 |
20 | if axis == .horizontal {
21 | frame(height: 1)
22 | } else {
23 | frame(width: 1)
24 | }
25 | }
26 |
27 | required init?(coder: NSCoder) {
28 | fatalError("init(coder:) has not been implemented")
29 | }
30 | }
31 |
32 |
--------------------------------------------------------------------------------
/documentation/NavButton.md:
--------------------------------------------------------------------------------
1 | # NavButton
2 |
3 | ``` swift
4 | @available(iOS 9.0, *) public class NavButton: Button
5 | ```
6 |
7 | ## Inheritance
8 |
9 | [`Button`](/Button)
10 |
11 | ## Initializers
12 |
13 | ### `init(title:tapHandler:destination:style:titleColor:backgroundColor:)`
14 |
15 | ``` swift
16 | public init(title: String, tapHandler: (() -> Void)? = nil, destination: @escaping () -> UIViewController, style: Navigate.NavigationStyle, titleColor: UIColor? = nil, backgroundColor: UIColor? = nil)
17 | ```
18 |
19 | ### `init(tapHandler:destination:style:labelView:)`
20 |
21 | ``` swift
22 | public init(tapHandler: (() -> Void)? = nil, destination: @escaping () -> UIViewController, style: Navigate.NavigationStyle, labelView: () -> UIView)
23 | ```
24 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: [0xLeif] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
4 | patreon: # Replace with a single Patreon username
5 | open_collective: # Replace with a single Open Collective username
6 | ko_fi: # Replace with a single Ko-Fi username
7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
9 | liberapay: # Replace with a single Liberapay username
10 | issuehunt: # Replace with a single IssueHunt username
11 | otechie: # Replace with a single Otechie username
12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
13 |
--------------------------------------------------------------------------------
/documentation/LoadingView.md:
--------------------------------------------------------------------------------
1 | # LoadingView
2 |
3 | ``` swift
4 | @available(iOS 9.0, *) public class LoadingView: UIActivityIndicatorView
5 | ```
6 |
7 | ## Inheritance
8 |
9 | `UIActivityIndicatorView`
10 |
11 | ## Initializers
12 |
13 | ### `init(forStyle:)`
14 |
15 | ``` swift
16 | public init(forStyle style: UIActivityIndicatorView.Style? = nil)
17 | ```
18 |
19 | ## Methods
20 |
21 | ### `start()`
22 |
23 | ``` swift
24 | @discardableResult func start() -> Self
25 | ```
26 |
27 | ### `stop()`
28 |
29 | ``` swift
30 | @discardableResult func stop() -> Self
31 | ```
32 |
33 | ### `play(_:)`
34 |
35 | ``` swift
36 | @discardableResult func play(_ ifTrue: () -> Bool) -> Self
37 | ```
38 |
39 | ### `color(_:)`
40 |
41 | ``` swift
42 | @discardableResult func color(_ color: UIColor) -> Self
43 | ```
44 |
--------------------------------------------------------------------------------
/documentation/Button.md:
--------------------------------------------------------------------------------
1 | # Button
2 |
3 | ``` swift
4 | @available(iOS 9.0, *) public class Button: UIButton
5 | ```
6 |
7 | ## Inheritance
8 |
9 | `UIButton`
10 |
11 | ## Initializers
12 |
13 | ### `init(title:titleColor:backgroundColor:forEvent:action:)`
14 |
15 | ``` swift
16 | public init(title: String, titleColor: UIColor? = nil, backgroundColor: UIColor? = nil, forEvent event: UIControl.Event = .touchUpInside, action: (() -> Void)? = nil)
17 | ```
18 |
19 | ### `init(forEvent:action:labelView:)`
20 |
21 | ``` swift
22 | public init(forEvent event: UIControl.Event = .touchUpInside, action: (() -> Void)? = nil, labelView: () -> UIView)
23 | ```
24 |
25 | ## Methods
26 |
27 | ### `setAction(_:)`
28 |
29 | ``` swift
30 | @discardableResult public func setAction(_ action: (() -> Void)?) -> Self
31 | ```
32 |
--------------------------------------------------------------------------------
/documentation/ZStack.md:
--------------------------------------------------------------------------------
1 | # ZStack
2 |
3 | ZStack:
4 | A view which stacks its children views in order
5 |
6 | ``` swift
7 | @available(iOS 9.0, *) public class ZStack: UIView
8 | ```
9 |
10 | ## Inheritance
11 |
12 | `UIView`
13 |
14 | ## Initializers
15 |
16 | ### `init(_:)`
17 |
18 | Create a ZStack
19 |
20 | ``` swift
21 | public init(_ closure: () -> [UIView])
22 | ```
23 |
24 | #### Parameters
25 |
26 | - closure: A trailing closure that accepts an array of views
27 |
28 | ### `init(_:)`
29 |
30 | Create a ZStack
31 |
32 | ``` swift
33 | public init(_ closure: () -> [UIView?])
34 | ```
35 |
36 | #### Parameters
37 |
38 | - closure: A trailing closure that accepts an array of optional views
39 |
40 | ## Properties
41 |
42 | ### `views`
43 |
44 | The views that the ZStack contains
45 |
46 | ``` swift
47 | var views: [UIView] = []
48 | ```
49 |
--------------------------------------------------------------------------------
/Sources/SwiftUIKit/Views/Switch.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Switch.swift
3 | //
4 | //
5 | // Created by Zach Eriksen on 11/2/19.
6 | //
7 |
8 | import UIKit
9 |
10 | @available(iOS 9.0, *)
11 | public class Switch: UISwitch {
12 | private var toggleChangedHandler: ((Bool) -> Void)?
13 |
14 | public init(
15 | isOn: Bool = false,
16 | toggleChanged: ((Bool) -> Void)? = nil
17 | ) {
18 | super.init(frame: .zero)
19 |
20 | self.isOn = isOn
21 | self.toggleChangedHandler = toggleChanged
22 | addTarget(self, action: #selector(handleValueChanged), for: .valueChanged)
23 | }
24 |
25 | required init?(coder: NSCoder) {
26 | fatalError("init(coder:) has not been implemented")
27 | }
28 |
29 | @objc private func handleValueChanged() {
30 | toggleChangedHandler?(isOn)
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/Sources/SwiftUIKit/Extensions/UITextView+SwiftUIKit.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UITextView+SwiftUIKit.swift
3 | // SwiftUIKit
4 | //
5 | // Created by Zach Eriksen on 7/7/20.
6 | //
7 |
8 | import UIKit
9 |
10 | public extension UITextView {
11 | @discardableResult
12 | func text(color: UIColor?) -> Self {
13 | textColor = color
14 |
15 | return self
16 | }
17 |
18 | @discardableResult
19 | func text(_ value: String?) -> Self {
20 | text = value
21 |
22 | return self
23 | }
24 |
25 | @discardableResult
26 | func tint(color: UIColor?) -> Self {
27 | tintColor = color
28 |
29 | return self
30 | }
31 |
32 | @discardableResult
33 | func set(delegate: UITextViewDelegate?) -> Self {
34 | self.delegate = delegate
35 |
36 | return self
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/Sources/SwiftUIKit/Extensions/UIControl+SwiftUIKit.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIControl+SwiftUIKit.swift
3 | // SwiftUIKit
4 | //
5 | // Created by Zach Eriksen on 7/7/20.
6 | //
7 |
8 | import UIKit
9 |
10 | public extension UIControl {
11 | /// Set `isEnabled` for the view
12 | @discardableResult
13 | func enabled(if value: Bool = true) -> Self {
14 | isEnabled = value
15 |
16 | return self
17 | }
18 |
19 | /// Set `isEnabled` to `true` for the view
20 | @discardableResult
21 | func enable() -> Self {
22 | enabled(if: true)
23 | }
24 |
25 | /// Set `isEnabled` to `false` for the view
26 | @discardableResult
27 | func disable() -> Self {
28 | enabled(if: false)
29 | }
30 |
31 | @discardableResult
32 | func tint(color: UIColor?) -> Self {
33 | tintColor = color
34 |
35 | return self
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/documentation/LoadingImage.md:
--------------------------------------------------------------------------------
1 | # LoadingImage
2 |
3 | ``` swift
4 | @available(iOS 9.0, *) public class LoadingImage: UIView
5 | ```
6 |
7 | ## Inheritance
8 |
9 | `UIView`
10 |
11 | ## Initializers
12 |
13 | ### `init(url:loadingTint:onErrorLoading:onCompletedLoading:)`
14 |
15 | ``` swift
16 | public init(url: URL? = nil, loadingTint: UIColor? = nil, onErrorLoading: ((LoadingImage?, Error?) -> Void)? = nil, onCompletedLoading: ((UIImage?) -> Void)? = nil)
17 | ```
18 |
19 | ## Methods
20 |
21 | ### `contentMode(_:)`
22 |
23 | ``` swift
24 | @discardableResult public func contentMode(_ mode: UIView.ContentMode) -> Self
25 | ```
26 |
27 | ### `onImageLoaded(_:)`
28 |
29 | ``` swift
30 | @discardableResult public func onImageLoaded(_ handler: @escaping (UIImage?) -> Void) -> Self
31 | ```
32 |
33 | ### `load(url:)`
34 |
35 | ``` swift
36 | @discardableResult public func load(url: URL?) -> Self
37 | ```
38 |
39 | ### `update(image:)`
40 |
41 | ``` swift
42 | public func update(image: UIImage)
43 | ```
44 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Describe the bug**
11 | A clear and concise description of what the bug is.
12 |
13 | **To Reproduce**
14 | Steps to reproduce the behavior:
15 | 1. Go to '...'
16 | 2. Click on '....'
17 | 3. Scroll down to '....'
18 | 4. See error
19 |
20 | **Expected behavior**
21 | A clear and concise description of what you expected to happen.
22 |
23 | **Screenshots**
24 | If applicable, add screenshots to help explain your problem.
25 |
26 | **Desktop (please complete the following information):**
27 | - OS: [e.g. iOS]
28 | - Browser [e.g. chrome, safari]
29 | - Version [e.g. 22]
30 |
31 | **Smartphone (please complete the following information):**
32 | - Device: [e.g. iPhone6]
33 | - OS: [e.g. iOS8.1]
34 | - Browser [e.g. stock browser, safari]
35 | - Version [e.g. 22]
36 |
37 | **Additional context**
38 | Add any other context about the problem here.
39 |
--------------------------------------------------------------------------------
/Sources/SwiftUIKit/Views/Slider.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Slider.swift
3 | //
4 | //
5 | // Created by Zach Eriksen on 11/4/19.
6 | //
7 |
8 | import UIKit
9 |
10 | @available(iOS 9.0, *)
11 | public class Slider: UISlider {
12 | private var valueChangedHandler: ((Float) -> Void)?
13 |
14 | public init(
15 | value: Float,
16 | from: Float,
17 | to: Float,
18 | valueChangedHandler: ((Float) -> Void)? = nil
19 | ) {
20 |
21 | super.init(frame: .zero)
22 |
23 | self.minimumValue = from
24 | self.maximumValue = to
25 | self.value = value
26 |
27 | self.valueChangedHandler = valueChangedHandler
28 | addTarget(self, action: #selector(handleValueChanged), for: .valueChanged)
29 | }
30 |
31 | required init?(coder: NSCoder) {
32 | fatalError("init(coder:) has not been implemented")
33 | }
34 |
35 | @objc private func handleValueChanged(sender: UISlider) {
36 | valueChangedHandler?(sender.value)
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/Sources/SwiftUIKit/Views/Spacer.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Spacer.swift
3 | // SwiftUIKit
4 | //
5 | // Created by Zach Eriksen on 10/29/19.
6 | //
7 |
8 | import UIKit
9 |
10 | @available(iOS 9.0, *)
11 | public class Spacer: UIView {
12 | private var height: Float?
13 | private var width: Float?
14 |
15 | public init(height: Float? = nil, width: Float? = nil) {
16 | self.height = height
17 | self.width = width
18 | super.init(frame: .zero)
19 |
20 | if let height = height {
21 | heightAnchor.constraint(equalToConstant: CGFloat(height)).isActive = true
22 | } else {
23 | setContentHuggingPriority(.defaultLow, for: .vertical)
24 | }
25 |
26 | if let width = width {
27 | widthAnchor.constraint(equalToConstant: CGFloat(width)).isActive = true
28 | } else {
29 | setContentHuggingPriority(.defaultLow, for: .horizontal)
30 | }
31 | }
32 |
33 | required init?(coder: NSCoder) {
34 | fatalError("init(coder:) has not been implemented")
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/Sources/SwiftUIKit/Containers/HScroll.swift:
--------------------------------------------------------------------------------
1 | //
2 | // HScroll.swift
3 | // SwiftUIKit
4 | //
5 | // Created by Zach Eriksen on 9/1/20.
6 | //
7 |
8 | import UIKit
9 |
10 | @available(iOS 9.0, *)
11 | public class HScroll: UIView {
12 |
13 | public init(
14 | withPadding padding: Float = 0,
15 | _ closure: () -> UIView
16 | ) {
17 | super.init(frame: .zero)
18 |
19 | let scrollableView = closure()
20 |
21 | let scrollView = ScrollView {
22 | scrollableView
23 | }
24 |
25 | embed {
26 | scrollView
27 | }
28 | .configure { parentView in
29 | NSLayoutConstraint.activate([
30 | scrollableView.topAnchor.constraint(equalTo: parentView.topAnchor, constant: CGFloat(padding)),
31 | scrollableView.bottomAnchor.constraint(equalTo: parentView.bottomAnchor, constant: CGFloat(-padding))
32 | ])
33 | }
34 | }
35 |
36 | required init?(coder: NSCoder) {
37 | fatalError("init(coder:) has not been implemented")
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/Sources/SwiftUIKit/Containers/VScroll.swift:
--------------------------------------------------------------------------------
1 | //
2 | // VScroll.swift
3 | // SwiftUIKit
4 | //
5 | // Created by Zach Eriksen on 9/1/20.
6 | //
7 |
8 | import UIKit
9 |
10 | @available(iOS 9.0, *)
11 | public class VScroll: UIView {
12 |
13 | public init(
14 | withPadding padding: Float = 0,
15 | _ closure: () -> UIView
16 | ) {
17 | super.init(frame: .zero)
18 |
19 | let scrollableView = closure()
20 |
21 | let scrollView = ScrollView {
22 | scrollableView
23 | }
24 |
25 | embed {
26 | scrollView
27 | }
28 | .configure { parentView in
29 | NSLayoutConstraint.activate([
30 | scrollableView.leadingAnchor.constraint(equalTo: parentView.leadingAnchor, constant: CGFloat(padding)),
31 | scrollableView.trailingAnchor.constraint(equalTo: parentView.trailingAnchor, constant: CGFloat(-padding))
32 | ])
33 | }
34 | }
35 |
36 | required init?(coder: NSCoder) {
37 | fatalError("init(coder:) has not been implemented")
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Zach Eriksen
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 |
--------------------------------------------------------------------------------
/.github/workflows/swift.yml:
--------------------------------------------------------------------------------
1 | name: Swift
2 |
3 | on: [push]
4 |
5 | jobs:
6 | build:
7 |
8 | runs-on: macOS-latest
9 | strategy:
10 | matrix:
11 | destination: ['platform=iOS Simulator,OS=13.1,name=iPhone 11']
12 | xcode: ['/Applications/Xcode_12.app/Contents/Developer']
13 | steps:
14 | - uses: actions/checkout@v1
15 | # Github Actions' machines do in fact have recent versions of Xcode,
16 | # but you may have to explicitly switch to them. We explicitly want
17 | # to use Xcode 12, so we use xcode-select to switch to it.
18 | - name: Switch to Xcode 12
19 | run: sudo xcode-select --switch /Applications/Xcode_12.app
20 | # Since we want to be running our tests from Xcode, we need to
21 | # generate an .xcodeproj file. Luckly, Swift Package Manager has
22 | # build in functionality to do so.
23 | - name: Generate xcodeproj
24 | run: swift package generate-xcodeproj
25 | # Finally, we invoke xcodebuild to run the tests on an iPhone 11
26 | # simulator.
27 | - name: Run tests
28 | run: xcodebuild test -destination 'name=iPhone 11' -scheme 'SwiftUIKit-Package'
29 |
--------------------------------------------------------------------------------
/Sources/SwiftUIKit/Views/ContainerView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ContainerView.swift
3 | // SwiftUIKit
4 | //
5 | // Created by Zach Eriksen on 5/17/20.
6 | //
7 |
8 | import UIKit
9 |
10 | @available(iOS 9.0, *)
11 | public class ContainerView: UIView {
12 | private weak var parentViewController: UIViewController?
13 | var viewController: UIViewController?
14 |
15 | public init(
16 | parent: UIViewController,
17 | child: () -> UIViewController
18 | ) {
19 | parentViewController = parent
20 | viewController = child()
21 | super.init(frame: .zero)
22 |
23 | embedViewController()
24 | }
25 |
26 | required init?(coder: NSCoder) {
27 | fatalError("init(coder:) has not been implemented")
28 | }
29 | }
30 |
31 | @available(iOS 9.0, *)
32 | private extension ContainerView {
33 | func embedViewController() {
34 | guard let parent = parentViewController,
35 | let child = viewController else {
36 | return
37 | }
38 |
39 | parent.addChild(child)
40 | child.didMove(toParent: parent)
41 |
42 | embed {
43 | child.view
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/Sources/SwiftUIKit/Containers/SafeAreaView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SafeAreaView.swift
3 | //
4 | //
5 | // Created by Zach Eriksen on 10/30/19.
6 | //
7 |
8 | import UIKit
9 |
10 | /// A View that respects the SafeArea
11 | @available(iOS 11.0, *)
12 | public class SafeAreaView: UIView {
13 |
14 | /// Create a SafeAreaView
15 | /// - Parameters:
16 | /// - closure: A trailing closure that accepts a view
17 | public init(_ closure: () -> UIView) {
18 | let view = closure()
19 | super.init(frame: .zero)
20 |
21 | view.translatesAutoresizingMaskIntoConstraints = false
22 | addSubview(view)
23 |
24 | let margins = layoutMarginsGuide
25 | let guide = safeAreaLayoutGuide
26 | NSLayoutConstraint.activate([
27 | view.leadingAnchor.constraint(equalTo: margins.leadingAnchor),
28 | view.trailingAnchor.constraint(equalTo: margins.trailingAnchor),
29 | view.topAnchor.constraint(equalToSystemSpacingBelow: guide.topAnchor, multiplier: 1.0),
30 | guide.bottomAnchor.constraint(equalToSystemSpacingBelow: view.bottomAnchor, multiplier: 1.0)
31 | ])
32 | }
33 |
34 | required init?(coder aDecoder: NSCoder) {
35 | fatalError("init(coder:) has not been implemented")
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/documentation/TextField.md:
--------------------------------------------------------------------------------
1 | # TextField
2 |
3 | ``` swift
4 | @available(iOS 9.0, *) public class TextField: UITextField
5 | ```
6 |
7 | ## Inheritance
8 |
9 | `UITextField`, `UITextFieldDelegate`
10 |
11 | ## Nested Type Aliases
12 |
13 | ### `WillValueChangeHandler`
14 |
15 | ``` swift
16 | public typealias WillValueChangeHandler = (_ sender: UITextField, _ newValue: String, _ input: String) -> Bool
17 | ```
18 |
19 | ### `DidValueChangeHandler`
20 |
21 | ``` swift
22 | public typealias DidValueChangeHandler = (String) -> Void
23 | ```
24 |
25 | ## Initializers
26 |
27 | ### `init(value:placeholder:keyboardType:)`
28 |
29 | ``` swift
30 | public init(value: String, placeholder: String, keyboardType type: UIKeyboardType)
31 | ```
32 |
33 | ## Methods
34 |
35 | ### `willInputUpdateHandler(_:)`
36 |
37 | ``` swift
38 | @discardableResult public func willInputUpdateHandler(_ willInputUpdateHandler: @escaping WillValueChangeHandler) -> Self
39 | ```
40 |
41 | ### `inputHandler(_:)`
42 |
43 | ``` swift
44 | @discardableResult public func inputHandler(_ inputHandler: @escaping DidValueChangeHandler) -> Self
45 | ```
46 |
47 | ### `textField(_:shouldChangeCharactersIn:replacementString:)`
48 |
49 | ``` swift
50 | public func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool
51 | ```
52 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.1
2 | // The swift-tools-version declares the minimum version of Swift required to build this package.
3 |
4 | import PackageDescription
5 |
6 | let package = Package(
7 | name: "SwiftUIKit",
8 | products: [
9 | // Products define the executables and libraries produced by a package, and make them visible to other packages.
10 | .library(
11 | name: "SwiftUIKit",
12 | targets: ["SwiftUIKit"]),
13 | ],
14 | dependencies: [
15 | // Dependencies declare other packages that this package depends on.
16 | // .package(url: /* package url */, from: "1.0.0"),
17 | .package(url: "https://github.com/0xLeif/Observation", from: "0.0.2"),
18 | .package(url: "https://github.com/0xLet/DataObject.git", from: "1.0.0"),
19 | ],
20 | targets: [
21 | // Targets are the basic building blocks of a package. A target can define a module or a test suite.
22 | // Targets can depend on other targets in this package, and on products in packages which this package depends on.
23 | .target(
24 | name: "SwiftUIKit",
25 | dependencies: [
26 | "Observation",
27 | "DataObject"
28 | ]),
29 | .testTarget(
30 | name: "SwiftUIKitTests",
31 | dependencies: ["SwiftUIKit"]),
32 | ]
33 | )
34 |
--------------------------------------------------------------------------------
/documentation/MultiLineField.md:
--------------------------------------------------------------------------------
1 | # MultiLineField
2 |
3 | ``` swift
4 | @available(iOS 9.0, *) public class MultiLineField: UITextView
5 | ```
6 |
7 | ## Inheritance
8 |
9 | `UITextView`, `UITextViewDelegate`
10 |
11 | ## Nested Type Aliases
12 |
13 | ### `WillValueChangeHandler`
14 |
15 | ``` swift
16 | public typealias WillValueChangeHandler = (_ sender: UITextView, _ newValue: String, _ input: String) -> Bool
17 | ```
18 |
19 | ### `DidValueChangeHandler`
20 |
21 | ``` swift
22 | public typealias DidValueChangeHandler = (String) -> Void
23 | ```
24 |
25 | ## Initializers
26 |
27 | ### `init(value:keyboardType:)`
28 |
29 | ``` swift
30 | public init(value: String, keyboardType type: UIKeyboardType)
31 | ```
32 |
33 | ## Methods
34 |
35 | ### `willInputUpdateHandler(_:)`
36 |
37 | ``` swift
38 | @discardableResult public func willInputUpdateHandler(_ willInputUpdateHandler: @escaping WillValueChangeHandler) -> Self
39 | ```
40 |
41 | ### `inputHandler(_:)`
42 |
43 | ``` swift
44 | @discardableResult public func inputHandler(_ inputHandler: @escaping DidValueChangeHandler) -> Self
45 | ```
46 |
47 | ### `textView(_:shouldChangeTextIn:replacementText:)`
48 |
49 | ``` swift
50 | public func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool
51 | ```
52 |
53 | ### `textViewDidChange(_:)`
54 |
55 | ``` swift
56 | public func textViewDidChange(_ textView: UITextView)
57 | ```
58 |
--------------------------------------------------------------------------------
/Sources/SwiftUIKit/Views/LoadingView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LoadingView.swift
3 | //
4 | //
5 | // Created by Zach Eriksen on 11/27/19.
6 | //
7 |
8 | import UIKit
9 |
10 | @available(iOS 9.0, *)
11 | public class LoadingView: UIActivityIndicatorView {
12 | public init(forStyle style: UIActivityIndicatorView.Style? = nil) {
13 | guard let style = style else {
14 | if #available(iOS 13.0, *) {
15 | super.init(style: .medium)
16 | } else {
17 | super.init(style: .white)
18 | }
19 | return
20 | }
21 |
22 | super.init(style: style)
23 | }
24 |
25 | required init(coder: NSCoder) {
26 | fatalError("init(coder:) has not been implemented")
27 | }
28 | }
29 |
30 | @available(iOS 9.0, *)
31 | public extension LoadingView {
32 | @discardableResult
33 | func start() -> Self {
34 | startAnimating()
35 |
36 | return self
37 | }
38 |
39 | @discardableResult
40 | func stop() -> Self {
41 | stopAnimating()
42 |
43 | return self
44 | }
45 |
46 | @discardableResult
47 | func play(_ ifTrue: () -> Bool) -> Self {
48 | return ifTrue() ? start() : stop()
49 | }
50 |
51 | @discardableResult
52 | func color(_ color: UIColor) -> Self {
53 | self.color = color
54 |
55 | return self
56 | }
57 |
58 | }
59 |
--------------------------------------------------------------------------------
/Sources/SwiftUIKit/Containers/ZStack.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ZStack.swift
3 | //
4 | //
5 | // Created by Zach Eriksen on 11/27/19.
6 | //
7 |
8 | import UIKit
9 |
10 | /// ZStack:
11 | /// A view which stacks its children views in order
12 | @available(iOS 9.0, *)
13 | public class ZStack: UIView {
14 | /// The views that the ZStack contains
15 | public var views: [UIView] = []
16 |
17 | /// Create a ZStack
18 | /// - Parameters:
19 | /// - closure: A trailing closure that accepts an array of views
20 | public init(_ closure: () -> [UIView]) {
21 | views = closure()
22 | super.init(frame: .zero)
23 |
24 | views.forEach {
25 | $0.translatesAutoresizingMaskIntoConstraints = false
26 | addSubview($0)
27 | $0.sizeToFit()
28 | }
29 | }
30 |
31 | /// Create a ZStack
32 | /// - Parameters:
33 | /// - closure: A trailing closure that accepts an array of optional views
34 | public init(_ closure: () -> [UIView?]) {
35 | views = closure()
36 | .compactMap { $0 }
37 | super.init(frame: .zero)
38 |
39 | views.forEach {
40 | $0.translatesAutoresizingMaskIntoConstraints = false
41 | addSubview($0)
42 | $0.sizeToFit()
43 | }
44 | }
45 |
46 | required init?(coder aDecoder: NSCoder) {
47 | fatalError("init(coder:) has not been implemented")
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/Sources/SwiftUIKit/Views/ObservedView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ObservedView.swift
3 | //
4 | //
5 | // Created by Zach Eriksen on 8/25/20.
6 | //
7 |
8 | import E
9 | import UIKit
10 | import Chain
11 | import Observation
12 |
13 | @available(iOS 9.0, *)
14 | public class ObservedView: UIView where View: UIView {
15 | private var observedValue: ObservedValue = ObservedValue()
16 |
17 | private var observedView: View
18 | private var onChangeHandler: (Value?, View) -> Void
19 |
20 | public init(
21 | view: View,
22 | initialValue: Value?,
23 | onChangeHandler: @escaping (_ newValue: Value?, _ view: View) -> Void
24 | ) {
25 | self.observedView = view
26 | self.onChangeHandler = onChangeHandler
27 | super.init(frame: .zero)
28 |
29 | observedValue.didChangeHandler = .complete(
30 | .void { [weak self] in
31 | self?.onChange()
32 | }
33 | )
34 |
35 | if let initialValue = initialValue {
36 | observedValue.update(value: initialValue)
37 | }
38 |
39 | embed {
40 | view
41 | }
42 | }
43 |
44 | required init?(coder: NSCoder) {
45 | fatalError("init(coder:) has not been implemented")
46 | }
47 |
48 | @discardableResult
49 | public func update(value: Value) -> Variable {
50 | observedValue.update(value: value)
51 | }
52 |
53 | private func onChange() {
54 | onChangeHandler(observedValue.value, observedView)
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/documentation/List.md:
--------------------------------------------------------------------------------
1 | # List
2 |
3 | ``` swift
4 | @available(iOS 9.0, *) public class List: UITableView
5 | ```
6 |
7 | ## Inheritance
8 |
9 | `UITableView`, `UITableViewDataSource`, `UITableViewDelegate`
10 |
11 | ## Initializers
12 |
13 | ### `init(defaultCellHeight:estimatedCellHeight:_:)`
14 |
15 | ``` swift
16 | public init(defaultCellHeight: Float? = nil, estimatedCellHeight: Float? = nil, _ closure: () -> [UIView])
17 | ```
18 |
19 | ## Methods
20 |
21 | ### `didSelectHandler(_:)`
22 |
23 | ``` swift
24 | @discardableResult func didSelectHandler(_ action: @escaping (UIView) -> Void) -> Self
25 | ```
26 |
27 | ### `configureCell(_:)`
28 |
29 | ``` swift
30 | @discardableResult func configureCell(_ action: @escaping (UITableViewCell) -> Void) -> Self
31 | ```
32 |
33 | ### `numberOfSections(in:)`
34 |
35 | ``` swift
36 | public func numberOfSections(in tableView: UITableView) -> Int
37 | ```
38 |
39 | ### `tableView(_:numberOfRowsInSection:)`
40 |
41 | ``` swift
42 | public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
43 | ```
44 |
45 | ### `tableView(_:cellForRowAt:)`
46 |
47 | ``` swift
48 | public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
49 | ```
50 |
51 | ### `tableView(_:heightForRowAt:)`
52 |
53 | ``` swift
54 | public func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat
55 | ```
56 |
57 | ### `tableView(_:estimatedHeightForRowAt:)`
58 |
59 | ``` swift
60 | public func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat
61 | ```
62 |
63 | ### `tableView(_:didSelectRowAt:)`
64 |
65 | ``` swift
66 | public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
67 | ```
68 |
--------------------------------------------------------------------------------
/Sources/SwiftUIKit/Views/EffectView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // EffectView.swift
3 | // SwiftUIKit
4 | //
5 | // Created by Zach Eriksen on 1/29/20.
6 | //
7 |
8 | import UIKit
9 |
10 | @available(iOS 9.0, *)
11 | public class EffectView: UIVisualEffectView {
12 | public init(
13 | for effect: UIVisualEffect? = nil,
14 | closure: () -> UIView
15 | ) {
16 | super.init(effect: effect)
17 |
18 | contentView.embed {
19 | closure()
20 | }
21 | }
22 |
23 | required init?(coder aDecoder: NSCoder) {
24 | fatalError("init(coder:) has not been implemented")
25 | }
26 | }
27 |
28 | @available(iOS 10.0, *)
29 | public class BlurView: UIVisualEffectView {
30 | public init(
31 | style blur: UIBlurEffect.Style = UIBlurEffect.Style.regular,
32 | closure: () -> UIView
33 | ) {
34 | super.init(effect: UIBlurEffect(style: blur))
35 |
36 | contentView.embed {
37 | closure()
38 | }
39 | }
40 |
41 | required init?(coder aDecoder: NSCoder) {
42 | fatalError("init(coder:) has not been implemented")
43 | }
44 | }
45 |
46 | @available(iOS 13.0, *)
47 | public class VibrancyView: UIVisualEffectView {
48 | public init(
49 | blurStyle blur: UIBlurEffect.Style = UIBlurEffect.Style.regular,
50 | vibrancyStyle vibrancy: UIVibrancyEffectStyle = .fill,
51 | closure: () -> UIView
52 | ) {
53 | super.init(effect: UIVibrancyEffect(blurEffect: UIBlurEffect(style: blur),
54 | style: vibrancy))
55 |
56 | contentView.embed {
57 | closure()
58 | }
59 | }
60 |
61 | required init?(coder aDecoder: NSCoder) {
62 | fatalError("init(coder:) has not been implemented")
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/Sources/SwiftUIKit/Views/NavButton.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NavButton.swift
3 | //
4 | //
5 | // Created by Zach Eriksen on 11/4/19.
6 | //
7 |
8 | import UIKit
9 |
10 | @available(iOS 9.0, *)
11 | public class NavButton: Button {
12 | private var destination: () -> UIViewController
13 | private var style: Navigate.NavigationStyle
14 |
15 | public init(
16 | title: String,
17 | tapHandler: (() -> Void)? = nil,
18 | destination: @escaping () -> UIViewController,
19 | style: Navigate.NavigationStyle,
20 | titleColor: UIColor? = nil,
21 | backgroundColor: UIColor? = nil
22 | ) {
23 |
24 | self.destination = destination
25 | self.style = style
26 |
27 | super.init(
28 | title: title,
29 | titleColor: titleColor,
30 | backgroundColor: backgroundColor,
31 | forEvent: .touchUpInside,
32 | action: {
33 | tapHandler?()
34 | Navigate.shared.go(destination(), style: style)
35 | }
36 | )
37 | }
38 |
39 |
40 | public init(
41 | tapHandler: (() -> Void)? = nil,
42 | destination: @escaping () -> UIViewController,
43 | style: Navigate.NavigationStyle,
44 | labelView: () -> UIView
45 | ) {
46 |
47 | self.destination = destination
48 | self.style = style
49 |
50 | super.init(
51 | forEvent: .touchUpInside,
52 | action: {
53 | tapHandler?()
54 | Navigate.shared.go(destination(), style: style)
55 | },
56 | labelView: labelView
57 | )
58 | }
59 |
60 | required init?(coder: NSCoder) {
61 | fatalError("init(coder:) has not been implemented")
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/Sources/SwiftUIKit/Views/Button.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Zach Eriksen on 10/30/19.
6 | //
7 |
8 | import UIKit
9 |
10 | @available(iOS 9.0, *)
11 | public class Button: UIButton {
12 | private var action: (() -> Void)?
13 |
14 | public init(
15 | title: String,
16 | titleColor: UIColor? = nil,
17 | backgroundColor: UIColor? = nil,
18 | forEvent event: UIControl.Event = .touchUpInside,
19 | action: (() -> Void)? = nil
20 | ) {
21 | self.action = action
22 | super.init(frame: .zero)
23 |
24 | self.backgroundColor = backgroundColor
25 | self.setTitleColor(titleColor ?? .systemBlue, for: .normal)
26 | self.setTitle(title, for: .normal)
27 | self.addTarget(self, action: #selector(handleButtonTap), for: event)
28 |
29 | accessibility(label: title, traits: .button)
30 | }
31 |
32 | public init(
33 | forEvent event: UIControl.Event = .touchUpInside,
34 | action: (() -> Void)? = nil,
35 | labelView: () -> UIView
36 | ) {
37 | self.action = action
38 | super.init(frame: .zero)
39 |
40 | embed {
41 | labelView()
42 | .gesture { UITapGestureRecognizer(target: self, action: #selector(handleButtonTap)) }
43 | }
44 |
45 | self.addTarget(self, action: #selector(handleButtonTap), for: event)
46 | }
47 |
48 | @discardableResult
49 | public func setAction(_ action: (() -> Void)?) -> Self {
50 | self.action = action
51 |
52 | return self
53 | }
54 |
55 | required init?(coder: NSCoder) {
56 | fatalError("init(coder:) has not been implemented")
57 | }
58 |
59 | @objc private func handleButtonTap() {
60 | action?()
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/Tests/SwiftUIKitTests/ADA/BasicADATests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // BasicADATests.swift
3 | //
4 | //
5 | // Created by Zach Eriksen on 3/2/20.
6 | //
7 |
8 | import Foundation
9 | import XCTest
10 | @testable import SwiftUIKit
11 |
12 | @available(iOS 9.0, *)
13 | final class BasicADATests: XCTestCase {
14 | func testLabelADA() {
15 |
16 | let label = Label("SomeString")
17 | .accessibility(identifier: "SomeID")
18 | .padding()
19 | .padding()
20 | .padding()
21 | .debug()
22 |
23 | XCTAssertEqual(label.accessibilityLabel, "SomeString")
24 | XCTAssertEqual(label.accessibilityIdentifier, "SomeID")
25 | XCTAssertEqual(label.accessibilityTraits, .staticText)
26 | }
27 |
28 | func testButtonADA() {
29 | let button = Button(title: "SomeString") { print("Hello") }
30 | .accessibility(label: nil)
31 |
32 | XCTAssertEqual(button.accessibilityLabel, "SomeString")
33 | XCTAssertEqual(button.accessibilityIdentifier, nil)
34 | XCTAssertEqual(button.accessibilityTraits, .button)
35 | }
36 |
37 | func testComplexViewADA() {
38 | let view = UIView {
39 | UIView {
40 | HStack {
41 | [
42 | Label("Hello World"),
43 | Label("Ipsum")
44 | ]
45 | }
46 | }
47 | }
48 | .accessibility(identifier: "mainView")
49 |
50 | let accessibilityLabels = ["Hello World", "Ipsum"]
51 |
52 | XCTAssertEqual(view.allSubviews.compactMap { $0.accessibilityLabel }, accessibilityLabels)
53 | XCTAssertEqual(view.accessibilityIdentifier, "mainView")
54 | XCTAssertEqual(view.accessibilityTraits, .none)
55 | XCTAssertEqual(view.shouldGroupAccessibilityChildren, false)
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/documentation/HStack.md:
--------------------------------------------------------------------------------
1 | # HStack
2 |
3 | Horizontal StackView
4 |
5 | ``` swift
6 | @available(iOS 9.0, *) public class HStack: UIView
7 | ```
8 |
9 | ## Inheritance
10 |
11 | `UIView`
12 |
13 | ## Initializers
14 |
15 | ### `init(withSpacing:padding:alignment:distribution:_:)`
16 |
17 | Create a HStack
18 |
19 | ``` swift
20 | public init(withSpacing spacing: Float = 0, padding: Float = 0, alignment: UIStackView.Alignment = .fill, distribution: UIStackView.Distribution = .fill, _ closure: () -> [UIView])
21 | ```
22 |
23 | #### Parameters
24 |
25 | - withSpacing: The amount of spacing between each child view
26 | - padding: The amount of space between this view and its parent view
27 | - alignment: The layout of arranged views perpendicular to the stack view’s axis (source: UIStackView.Alignment)
28 | - distribution: The layout that defines the size and position of the arranged views along the stack view’s axis (source: UIStackView.Distribution)
29 | - closure: A trailing closure that accepts an array of views
30 |
31 | ### `init(withSpacing:padding:alignment:distribution:_:)`
32 |
33 | Create a HStack that accepts an array of UIView?
34 |
35 | ``` swift
36 | public init(withSpacing spacing: Float = 0, padding: Float = 0, alignment: UIStackView.Alignment = .fill, distribution: UIStackView.Distribution = .fill, _ closure: () -> [UIView?])
37 | ```
38 |
39 | #### Parameters
40 |
41 | - withSpacing: The amount of spacing between each child view
42 | - padding: The amount of space between this view and its parent view
43 | - alignment: The layout of arranged views perpendicular to the stack view’s axis (source: UIStackView.Alignment)
44 | - distribution: The layout that defines the size and position of the arranged views along the stack view’s axis (source: UIStackView.Distribution)
45 | - closure: A trailing closure that accepts an array of optional views
46 |
47 | ## Methods
48 |
49 | ### `update(views:)`
50 |
51 | ``` swift
52 | public func update(views closure: (inout [UIView]) -> Void) -> Self
53 | ```
54 |
--------------------------------------------------------------------------------
/documentation/VStack.md:
--------------------------------------------------------------------------------
1 | # VStack
2 |
3 | Vertical StackView
4 |
5 | ``` swift
6 | @available(iOS 9.0, *) public class VStack: UIView
7 | ```
8 |
9 | ## Inheritance
10 |
11 | `UIView`
12 |
13 | ## Initializers
14 |
15 | ### `init(withSpacing:padding:alignment:distribution:_:)`
16 |
17 | Create a VStack
18 |
19 | ``` swift
20 | public init(withSpacing spacing: Float = 0, padding: Float = 0, alignment: UIStackView.Alignment = .fill, distribution: UIStackView.Distribution = .fill, _ closure: () -> [UIView])
21 | ```
22 |
23 | #### Parameters
24 |
25 | - withSpacing: The amount of spacing between each child view
26 | - padding: The amount of space between this view and its parent view
27 | - alignment: The layout of arranged views perpendicular to the stack view’s axis (source: UIStackView.Alignment)
28 | - distribution: The layout that defines the size and position of the arranged views along the stack view’s axis (source: UIStackView.Distribution)
29 | - closure: A trailing closure that accepts an array of views
30 |
31 | ### `init(withSpacing:padding:alignment:distribution:_:)`
32 |
33 | Create a VStack that accepts an array of UIView?
34 |
35 | ``` swift
36 | public init(withSpacing spacing: Float = 0, padding: Float = 0, alignment: UIStackView.Alignment = .fill, distribution: UIStackView.Distribution = .fill, _ closure: () -> [UIView?])
37 | ```
38 |
39 | #### Parameters
40 |
41 | - withSpacing: The amount of spacing between each child view
42 | - padding: The amount of space between this view and its parent view
43 | - alignment: The layout of arranged views perpendicular to the stack view’s axis (source: UIStackView.Alignment)
44 | - distribution: The layout that defines the size and position of the arranged views along the stack view’s axis (source: UIStackView.Distribution)
45 | - closure: A trailing closure that accepts an array of optional views
46 |
47 | ## Methods
48 |
49 | ### `update(views:)`
50 |
51 | ``` swift
52 | @discardableResult public func update(views closure: (inout [UIView]) -> Void) -> Self
53 | ```
54 |
--------------------------------------------------------------------------------
/Sources/SwiftUIKit/Views/MultiLineField.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MultiLineField.swift
3 | //
4 | //
5 | // Created by Zach Eriksen on 11/30/19.
6 | //
7 |
8 | import UIKit
9 |
10 | @available(iOS 9.0, *)
11 | public class MultiLineField: UITextView {
12 | public typealias WillValueChangeHandler = (_ sender: UITextView, _ newValue: String, _ input: String) -> Bool
13 | public typealias DidValueChangeHandler = (String) -> Void
14 |
15 | private var willInputUpdateHandler: WillValueChangeHandler?
16 | private var inputHandler: DidValueChangeHandler?
17 |
18 | public init(value: String, keyboardType type: UIKeyboardType) {
19 | super.init(frame: .zero, textContainer: nil)
20 |
21 | self.text = value
22 | self.keyboardType = type
23 |
24 | delegate = self
25 | }
26 |
27 | required init?(coder: NSCoder) {
28 | fatalError("init(coder:) has not been implemented")
29 | }
30 |
31 | @discardableResult
32 | public func willInputUpdateHandler(_ willInputUpdateHandler: @escaping WillValueChangeHandler) -> Self {
33 | self.willInputUpdateHandler = willInputUpdateHandler
34 |
35 | return self
36 | }
37 |
38 | @discardableResult
39 | public func inputHandler(_ inputHandler: @escaping DidValueChangeHandler) -> Self {
40 | self.inputHandler = inputHandler
41 |
42 | return self
43 | }
44 | }
45 |
46 | @available(iOS 9.0, *)
47 | extension MultiLineField: UITextViewDelegate {
48 | public func textView(
49 | _ textView: UITextView,
50 | shouldChangeTextIn range: NSRange,
51 | replacementText text: String
52 | ) -> Bool {
53 | let newValue = NSString(string: textView.text ?? "").replacingCharacters(in: range, with: text)
54 |
55 | return willInputUpdateHandler?(textView, newValue, text) ?? true
56 | }
57 |
58 | public func textViewDidChange(_ textView: UITextView) {
59 | inputHandler?(textView.text ?? "")
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/documentation/_Sidebar.md:
--------------------------------------------------------------------------------
1 |
2 | Types
3 |
4 | - [BlurView](/BlurView)
5 | - [Button](/Button)
6 | - [CollectionView](/CollectionView)
7 | - [ContainerView](/ContainerView)
8 | - [Divider](/Divider)
9 | - [EffectView](/EffectView)
10 | - [HScroll](/HScroll)
11 | - [HStack](/HStack)
12 | - [Image](/Image)
13 | - [Label](/Label)
14 | - [List](/List)
15 | - [LoadingImage](/LoadingImage)
16 | - [LoadingView](/LoadingView)
17 | - [Map](/Map)
18 | - [MapPoint](/MapPoint)
19 | - [MultiLineField](/MultiLineField)
20 | - [NavButton](/NavButton)
21 | - [Navigate](/Navigate)
22 | - [Navigate.NavigationStyle](/Navigate.NavigationStyle)
23 | - [Navigate.ToastStyle](/Navigate.ToastStyle)
24 | - [ObservedView](/ObservedView)
25 | - [Padding](/Padding)
26 | - [SafeAreaView](/SafeAreaView)
27 | - [ScrollView](/ScrollView)
28 | - [Slider](/Slider)
29 | - [Spacer](/Spacer)
30 | - [Switch](/Switch)
31 | - [TableView](/TableView)
32 | - [TextField](/TextField)
33 | - [VScroll](/VScroll)
34 | - [VStack](/VStack)
35 | - [VibrancyView](/VibrancyView)
36 | - [ViewController](/ViewController)
37 | - [WebView](/WebView)
38 | - [ZStack](/ZStack)
39 |
40 |
41 |
42 |
43 | Protocols
44 |
45 | - [CellConfigurable](/CellConfigurable)
46 | - [CellDisplayable](/CellDisplayable)
47 | - [CellUpdatable](/CellUpdatable)
48 | - [DataIdentifiable](/DataIdentifiable)
49 |
50 |
51 |
52 |
53 | Global Typealiases
54 |
55 | - [AttributedString](/AttributedString)
56 | - [AttributedStringKey](/AttributedStringKey)
57 | - [BarButton](/BarButton)
58 | - [CollectionViewCell](/CollectionViewCell)
59 | - [StringAttributes](/StringAttributes)
60 | - [TableDidSelectIndexPathHandler](/TableDidSelectIndexPathHandler)
61 | - [TableHeaderFooterViewHandler](/TableHeaderFooterViewHandler)
62 | - [TableHighlightIndexPathHandler](/TableHighlightIndexPathHandler)
63 | - [TableViewCell](/TableViewCell)
64 |
65 |
66 |
--------------------------------------------------------------------------------
/documentation/WebView.md:
--------------------------------------------------------------------------------
1 | # WebView
2 |
3 | ``` swift
4 | @available(iOS 9.0, *) public class WebView: WKWebView
5 | ```
6 |
7 | ## Inheritance
8 |
9 | `WKWebView`
10 |
11 | ## Initializers
12 |
13 | ### `init()`
14 |
15 | ``` swift
16 | public init()
17 | ```
18 |
19 | ### `init(baseURL:htmlString:)`
20 |
21 | ``` swift
22 | convenience init(baseURL: URL? = nil, htmlString: () -> String)
23 | ```
24 |
25 | ### `init(url:)`
26 |
27 | ``` swift
28 | convenience init(url: URL)
29 | ```
30 |
31 | ### `init(request:)`
32 |
33 | ``` swift
34 | convenience init(request: URLRequest)
35 | ```
36 |
37 | ### `init(URL:allowingReadAccessTo:)`
38 |
39 | ``` swift
40 | convenience init(URL: URL, allowingReadAccessTo readAccessURL: URL)
41 | ```
42 |
43 | ### `init(data:mimeType:characterEncodingName:baseURL:)`
44 |
45 | ``` swift
46 | convenience init(data: Data, mimeType MIMEType: String, characterEncodingName: String, baseURL: URL)
47 | ```
48 |
49 | ## Methods
50 |
51 | ### `loadHTMLString(baseURL:htmlString:)`
52 |
53 | ``` swift
54 | @discardableResult func loadHTMLString(baseURL: URL? = nil, htmlString: () -> String) -> Self
55 | ```
56 |
57 | ### `load(url:)`
58 |
59 | ``` swift
60 | @discardableResult func load(url: URL) -> Self
61 | ```
62 |
63 | ### `load(request:)`
64 |
65 | ``` swift
66 | @discardableResult func load(request: URLRequest) -> Self
67 | ```
68 |
69 | ### `loadFile(URL:allowingReadAccessTo:)`
70 |
71 | ``` swift
72 | @discardableResult func loadFile(URL: URL, allowingReadAccessTo readAccessURL: URL) -> Self
73 | ```
74 |
75 | ### `load(data:mimeType:characterEncodingName:baseURL:)`
76 |
77 | ``` swift
78 | @discardableResult func load(data: Data, mimeType MIMEType: String, characterEncodingName: String, baseURL: URL) -> Self
79 | ```
80 |
81 | ### `set(uiDelegate:)`
82 |
83 | ``` swift
84 | @discardableResult func set(uiDelegate delegate: WKUIDelegate) -> Self
85 | ```
86 |
87 | ### `set(navigationDelegate:)`
88 |
89 | ``` swift
90 | @discardableResult func set(navigationDelegate delegate: WKNavigationDelegate) -> Self
91 | ```
92 |
--------------------------------------------------------------------------------
/Sources/SwiftUIKit/Extensions/NSLayoutConstraint+SwiftUIKit.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NSLayoutConstraint+SwiftUIKit.swift
3 | // SwiftUIKit
4 | //
5 | // Created by Zach Eriksen on 5/17/20.
6 | //
7 |
8 | import UIKit
9 |
10 | @available(iOS 10.0, *)
11 | public extension NSLayoutConstraint {
12 |
13 | /// Check if the `constraint` is connected to the `anchor`
14 | func isConnected(toAnchor anchor: NSLayoutAnchor) -> Bool {
15 | firstAnchor == anchor || secondAnchor == anchor
16 | }
17 | }
18 |
19 | @available(iOS 10.0, *)
20 | public extension UIView {
21 |
22 | /// The leading constraints held by the view
23 | var leadingConstraints: [NSLayoutConstraint] {
24 | constraints.filter { (constraint) -> Bool in
25 | constraint.isConnected(toAnchor: leadingAnchor)
26 | }
27 | }
28 |
29 | /// The trailing constraints held by the view
30 | var trailingConstraints: [NSLayoutConstraint] {
31 | constraints.filter { (constraint) -> Bool in
32 | constraint.isConnected(toAnchor: trailingAnchor)
33 | }
34 | }
35 |
36 | /// The top constraints held by the view
37 | var topConstraints: [NSLayoutConstraint] {
38 | constraints.filter { (constraint) -> Bool in
39 | constraint.isConnected(toAnchor: topAnchor)
40 | }
41 | }
42 |
43 | /// The bottom constraints held by the view
44 | var bottomConstraints: [NSLayoutConstraint] {
45 | constraints.filter { (constraint) -> Bool in
46 | constraint.isConnected(toAnchor: bottomAnchor)
47 | }
48 | }
49 |
50 | /// The height constraints held by the view
51 | var heightConstraints: [NSLayoutConstraint] {
52 | constraints.filter { (constraint) -> Bool in
53 | constraint.isConnected(toAnchor: heightAnchor)
54 | }
55 | }
56 |
57 | /// The width constraints held by the view
58 | var widthConstraints: [NSLayoutConstraint] {
59 | constraints.filter { (constraint) -> Bool in
60 | constraint.isConnected(toAnchor: widthAnchor)
61 | }
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/documentation/Home.md:
--------------------------------------------------------------------------------
1 | # Types
2 |
3 | - [HScroll](HScroll.md)
4 | - [HStack](HStack.md):
5 | Horizontal StackView
6 | - [SafeAreaView](SafeAreaView.md):
7 | A View that respects the SafeArea
8 | - [VScroll](VScroll.md)
9 | - [VStack](VStack.md):
10 | Vertical StackView
11 | - [ZStack](ZStack.md):
12 | ZStack:
13 | A view which stacks its children views in order
14 | - [Padding](Padding.md)
15 | - [ViewController](ViewController.md)
16 | - [Navigate](Navigate.md)
17 | - [Navigate.NavigationStyle](Navigate_NavigationStyle.md)
18 | - [Navigate.ToastStyle](Navigate_ToastStyle.md)
19 | - [Button](Button.md)
20 | - [CollectionView](CollectionView.md)
21 | - [ContainerView](ContainerView.md)
22 | - [Divider](Divider.md)
23 | - [EffectView](EffectView.md)
24 | - [BlurView](BlurView.md)
25 | - [VibrancyView](VibrancyView.md)
26 | - [Image](Image.md)
27 | - [Label](Label.md)
28 | - [List](List.md)
29 | - [LoadingImage](LoadingImage.md)
30 | - [LoadingView](LoadingView.md)
31 | - [MapPoint](MapPoint.md)
32 | - [Map](Map.md)
33 | - [MultiLineField](MultiLineField.md)
34 | - [NavButton](NavButton.md)
35 | - [ObservedView](ObservedView.md)
36 | - [ScrollView](ScrollView.md)
37 | - [Slider](Slider.md)
38 | - [Spacer](Spacer.md)
39 | - [Switch](Switch.md)
40 | - [TableView](TableView.md)
41 | - [TextField](TextField.md)
42 | - [WebView](WebView.md)
43 |
44 | # Protocols
45 |
46 | - [CellDisplayable](CellDisplayable.md)
47 | - [DataIdentifiable](DataIdentifiable.md)
48 | - [CellUpdatable](CellUpdatable.md)
49 | - [CellConfigurable](CellConfigurable.md)
50 |
51 | # Global Typealiases
52 |
53 | - [AttributedString](AttributedString.md)
54 | - [AttributedStringKey](AttributedStringKey.md)
55 | - [StringAttributes](StringAttributes.md)
56 | - [BarButton](BarButton.md)
57 | - [CollectionViewCell](CollectionViewCell.md)
58 | - [TableViewCell](TableViewCell.md)
59 | - [TableHeaderFooterViewHandler](TableHeaderFooterViewHandler.md)
60 | - [TableDidSelectIndexPathHandler](TableDidSelectIndexPathHandler.md)
61 | - [TableHighlightIndexPathHandler](TableHighlightIndexPathHandler.md)
62 |
--------------------------------------------------------------------------------
/Sources/SwiftUIKit/Extensions/NSMutableAttributedString+SwiftUIKit.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NSMutableAttributedString+SwiftUIKit.swift
3 | //
4 | //
5 | // Created by Zach Eriksen on 12/11/19.
6 | //
7 |
8 | import Foundation
9 |
10 | public typealias AttributedString = NSMutableAttributedString
11 | public typealias AttributedStringKey = NSAttributedString.Key
12 | public typealias StringAttributes = [AttributedStringKey: Any]
13 |
14 | public extension StringAttributes {
15 | /// Create a dictionary with NSAttributedString.Key
16 | /// - Parameters:
17 | /// - attributes: A trailing closure that accepts a dictionary with NSAttributedString.Key
18 | init(_ attributes: () -> [AttributedStringKey: Any]) {
19 | self = attributes()
20 | }
21 |
22 | /// Create a dictionary with one NSAttributedString.Key
23 | /// - Parameters:
24 | /// - for: A NSAttributedString.Key for the String
25 | /// - value: The value for the attribute being modifed
26 | init(for key: AttributedStringKey, value: Any) {
27 | self = [key: value]
28 | }
29 |
30 | /// Add an AttributedStringKey with a value of type Any to the dictionary
31 | /// - Parameters:
32 | /// - key: A NSAttributedString.Key for the String
33 | /// - value: The value for the attribute being modifed
34 | @discardableResult
35 | mutating func add(key: AttributedStringKey, value: Any) -> StringAttributes {
36 | self[key] = value
37 |
38 | return self
39 | }
40 | }
41 |
42 | public extension NSMutableAttributedString {
43 |
44 | /// Simple Swift wrapper for setAttributes(_ attrs: [NSAttributedString.Key : Any]?, range: NSRange)
45 | /// - Parameters:
46 | /// - attributes: StringAttributes is a typealias for [NSAttributedString.Key : Any],
47 | /// - range: Closed Int Range. Example: 0 ... 3
48 | @discardableResult
49 | func set(attributes: StringAttributes, range: ClosedRange) -> Self {
50 | self.setAttributes(attributes, range: NSRange(location: range.lowerBound, length: range.upperBound))
51 |
52 | return self
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/Sources/SwiftUIKit/Extensions/UIViewController+SwiftUIKit.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIViewController+SwiftUIKit.swift
3 | //
4 | //
5 | // Created by Zach Eriksen on 11/4/19.
6 | //
7 |
8 | import UIKit
9 |
10 | @available(iOS 9.0, *)
11 | public class ViewController: UIViewController {
12 | private var closure: () -> UIView
13 |
14 | public init(_ closure: @escaping (() -> UIView)) {
15 | self.closure = closure
16 |
17 | super.init(nibName: nil, bundle: nil)
18 | }
19 |
20 | required init?(coder: NSCoder) {
21 | fatalError("init(coder:) has not been implemented")
22 | }
23 |
24 | public override func viewDidLoad() {
25 | super.viewDidLoad()
26 |
27 | view.embed(closure)
28 | }
29 | }
30 |
31 | @available(iOS 9.0, *)
32 | public extension UIViewController {
33 | convenience init(_ closure: (() -> UIView)) {
34 | self.init()
35 |
36 | view.embed(closure)
37 | }
38 |
39 | /// Go without the Navigation Controller using the current ViewController to present the new ViewController
40 | func go(
41 | to viewController: UIViewController,
42 | style: Navigate.NavigationStyle,
43 | completion: (() -> Void)? = nil
44 | ) {
45 | Navigate.shared.go(from: self,
46 | to: viewController,
47 | style: style,
48 | completion: completion)
49 | }
50 |
51 | /// Navigate with the Navigation Controller to the new ViewController
52 | func navigate(
53 | _ viewController: UIViewController,
54 | style: Navigate.NavigationStyle,
55 | completion: (() -> Void)? = nil
56 | ) {
57 | Navigate.shared.go(viewController,
58 | style: style,
59 | completion: completion)
60 | }
61 |
62 | @available(iOS 11.0, *)
63 | /// Create a ViewController with Safe Area
64 | class func safe(_ closure: (() -> UIView)) -> UIViewController {
65 | return UIViewController {
66 | SafeAreaView(closure)
67 | }
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/Sources/SwiftUIKit/Views/Image.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Image.swift
3 | //
4 | //
5 | // Created by Zach Eriksen on 11/4/19.
6 | //
7 |
8 | import UIKit
9 |
10 | @available(iOS 9.0, *)
11 | public class Image: UIImageView {
12 | public init(_ image: UIImage) {
13 | super.init(image: image)
14 | }
15 |
16 | public init(color: UIColor) {
17 | super.init(frame: .zero)
18 |
19 | self.image = Image.image(fromColor: color)
20 | }
21 |
22 | public init(named name: String) {
23 | super.init(frame: .zero)
24 |
25 | guard let image = UIImage(named: name) else {
26 | print("Image \(#function) Error!")
27 | print("Issue loading Image with name: \(name)")
28 | print("Error: Could not locate Image")
29 | update(color: .red)
30 | return
31 | }
32 |
33 | self.image = image
34 | }
35 |
36 | required init?(coder: NSCoder) {
37 | fatalError("init(coder:) has not been implemented")
38 | }
39 |
40 | @discardableResult
41 | public func contentMode(_ mode: UIView.ContentMode) -> Self {
42 | self.contentMode = mode
43 |
44 | return self
45 | }
46 |
47 | private func update(image: UIImage) {
48 | DispatchQueue.main.async { [weak self] in
49 | self?.image = image
50 | }
51 | }
52 |
53 | private func update(color: UIColor) {
54 | DispatchQueue.main.async { [weak self] in
55 | self?.image = Image.image(fromColor: color)
56 | }
57 | }
58 |
59 | private static func image(fromColor color: UIColor) -> UIImage? {
60 | let frame = CGRect(origin: .zero,
61 | size: CGSize(width: 1, height: 1))
62 |
63 | UIGraphicsBeginImageContext(frame.size)
64 |
65 | let context = UIGraphicsGetCurrentContext()
66 | context!.setFillColor(color.cgColor)
67 | context!.fill(frame)
68 |
69 | let image = UIGraphicsGetImageFromCurrentImageContext()
70 |
71 | UIGraphicsEndImageContext()
72 |
73 | return image
74 | }
75 | }
76 |
77 |
--------------------------------------------------------------------------------
/Sources/SwiftUIKit/Views/TextField.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TextField.swift
3 | //
4 | //
5 | // Created by Zach Eriksen on 11/4/19.
6 | //
7 |
8 | import UIKit
9 |
10 | @available(iOS 9.0, *)
11 | public class TextField: UITextField {
12 | public typealias WillValueChangeHandler = (_ sender: UITextField, _ newValue: String, _ input: String) -> Bool
13 | public typealias DidValueChangeHandler = (String) -> Void
14 |
15 | private var willInputUpdateHandler: WillValueChangeHandler?
16 | private var inputHandler: DidValueChangeHandler?
17 |
18 | public init(
19 | value: String,
20 | placeholder: String,
21 | keyboardType type: UIKeyboardType
22 | ) {
23 |
24 | super.init(frame: .zero)
25 |
26 | self.text = value
27 | self.placeholder = placeholder
28 | self.keyboardType = type
29 |
30 | delegate = self
31 | addTarget(self, action: #selector(valueDidChange), for: .editingChanged)
32 | }
33 |
34 | required init?(coder: NSCoder) {
35 | fatalError("init(coder:) has not been implemented")
36 | }
37 |
38 | @discardableResult
39 | public func willInputUpdateHandler(_ willInputUpdateHandler: @escaping WillValueChangeHandler) -> Self {
40 | self.willInputUpdateHandler = willInputUpdateHandler
41 |
42 | return self
43 | }
44 |
45 | @discardableResult
46 | public func inputHandler(_ inputHandler: @escaping DidValueChangeHandler) -> Self {
47 | self.inputHandler = inputHandler
48 |
49 | return self
50 | }
51 |
52 | @objc private func valueDidChange(_ textField: UITextField) {
53 | inputHandler?(textField.text ?? "")
54 | }
55 | }
56 |
57 | @available(iOS 9.0, *)
58 | extension TextField: UITextFieldDelegate {
59 | public func textField(
60 | _ textField: UITextField,
61 | shouldChangeCharactersIn range: NSRange,
62 | replacementString string: String
63 | ) -> Bool {
64 | let newValue = NSString(string: textField.text ?? "").replacingCharacters(in: range, with: string)
65 |
66 | return willInputUpdateHandler?(textField, newValue, string) ?? true
67 | }
68 | }
69 |
70 |
--------------------------------------------------------------------------------
/documentation/Label.md:
--------------------------------------------------------------------------------
1 | # Label
2 |
3 | ``` swift
4 | @available(iOS 9.0, *) public class Label: UILabel
5 | ```
6 |
7 | ## Inheritance
8 |
9 | `UILabel`
10 |
11 | ## Initializers
12 |
13 | ### `init(_:)`
14 |
15 | ``` swift
16 | public init(_ text: String)
17 | ```
18 |
19 | ### `init(_:)`
20 |
21 | ``` swift
22 | public init(_ attributedText: AttributedString)
23 | ```
24 |
25 | ## Methods
26 |
27 | ### `number(ofLines:)`
28 |
29 | ``` swift
30 | @discardableResult public func number(ofLines lines: Int) -> Self
31 | ```
32 |
33 | ### `font(_:)`
34 |
35 | ``` swift
36 | @discardableResult public func font(_ font: UIFont) -> Self
37 | ```
38 |
39 | ### `font(_:)`
40 |
41 | ``` swift
42 | @discardableResult public func font(_ textStyle: UIFont.TextStyle) -> Self
43 | ```
44 |
45 | ### `hideIfBlank()`
46 |
47 | ``` swift
48 | @discardableResult public func hideIfBlank() -> Self
49 | ```
50 |
51 | ### `apply(attributes:)`
52 |
53 | ``` swift
54 | @discardableResult public func apply(attributes: StringAttributes) -> Self
55 | ```
56 |
57 | ### `apply(attributes:range:)`
58 |
59 | ``` swift
60 | @discardableResult public func apply(attributes: StringAttributes, range: ClosedRange) -> Self
61 | ```
62 |
63 | ### `text(alignment:)`
64 |
65 | ``` swift
66 | @discardableResult public func text(alignment: NSTextAlignment) -> Self
67 | ```
68 |
69 | ### `text(color:)`
70 |
71 | ``` swift
72 | @discardableResult public func text(color: UIColor) -> Self
73 | ```
74 |
75 | ### `title1(_:)`
76 |
77 | ``` swift
78 | class func title1(_ text: String) -> Label
79 | ```
80 |
81 | ### `title2(_:)`
82 |
83 | ``` swift
84 | class func title2(_ text: String) -> Label
85 | ```
86 |
87 | ### `title3(_:)`
88 |
89 | ``` swift
90 | class func title3(_ text: String) -> Label
91 | ```
92 |
93 | ### `headline(_:)`
94 |
95 | ``` swift
96 | class func headline(_ text: String) -> Label
97 | ```
98 |
99 | ### `subheadline(_:)`
100 |
101 | ``` swift
102 | class func subheadline(_ text: String) -> Label
103 | ```
104 |
105 | ### `body(_:)`
106 |
107 | ``` swift
108 | class func body(_ text: String) -> Label
109 | ```
110 |
111 | ### `callout(_:)`
112 |
113 | ``` swift
114 | class func callout(_ text: String) -> Label
115 | ```
116 |
117 | ### `caption1(_:)`
118 |
119 | ``` swift
120 | class func caption1(_ text: String) -> Label
121 | ```
122 |
123 | ### `caption2(_:)`
124 |
125 | ``` swift
126 | class func caption2(_ text: String) -> Label
127 | ```
128 |
--------------------------------------------------------------------------------
/Tests/SwiftUIKitTests/ContainerView/ContainerViewTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ContainerViewTests.swift
3 | // SwiftUIKitTests
4 | //
5 | // Created by Zach Eriksen on 5/17/20.
6 | //
7 |
8 | import XCTest
9 | import UIKit
10 | import SwiftUIKit
11 |
12 | @available(iOS 9.0, *)
13 | class ContainerViewTests: XCTestCase {
14 |
15 | func testBasicContainerView() {
16 | let mainVC = UIViewController()
17 | let someVC = UIViewController()
18 | let containerView = ContainerView(parent: mainVC) {
19 | someVC
20 | }
21 |
22 | XCTAssertEqual(mainVC.children.count, 1)
23 | XCTAssertEqual(someVC.children.count, 0)
24 | XCTAssertEqual(mainVC.view.allSubviews.count, 0)
25 | XCTAssertEqual(someVC.view.allSubviews.count, 0)
26 | XCTAssertEqual(containerView.allSubviews.count, 1)
27 | }
28 |
29 | func testEmbedContainerView() {
30 | let mainVC = UIViewController()
31 | let someVC = UIViewController()
32 | let containerView = ContainerView(parent: mainVC) {
33 | someVC
34 | }
35 |
36 | XCTAssertEqual(mainVC.children.count, 1)
37 | XCTAssertEqual(someVC.children.count, 0)
38 | XCTAssertEqual(mainVC.view.allSubviews.count, 0)
39 | XCTAssertEqual(someVC.view.allSubviews.count, 0)
40 | XCTAssertEqual(containerView.allSubviews.count, 1)
41 |
42 | mainVC.view.embed {
43 | containerView
44 | }
45 |
46 | XCTAssertEqual(mainVC.children.count, 1)
47 | XCTAssertEqual(someVC.children.count, 0)
48 | XCTAssertEqual(mainVC.view.allSubviews.count, 2)
49 | XCTAssertEqual(someVC.view.allSubviews.count, 0)
50 | XCTAssertEqual(containerView.allSubviews.count, 1)
51 | }
52 |
53 | func testChildVCEmbedContainerView() {
54 | let mainVC = UIViewController()
55 | let someVC = UIViewController()
56 | let containerView = ContainerView(parent: mainVC) {
57 | someVC
58 | }
59 |
60 | XCTAssertEqual(mainVC.children.count, 1)
61 | XCTAssertEqual(someVC.children.count, 0)
62 | XCTAssertEqual(mainVC.view.allSubviews.count, 0)
63 | XCTAssertEqual(someVC.view.allSubviews.count, 0)
64 | XCTAssertEqual(containerView.allSubviews.count, 1)
65 |
66 | mainVC.view.embed {
67 | containerView
68 | }
69 |
70 | XCTAssertEqual(mainVC.children.count, 1)
71 | XCTAssertEqual(someVC.children.count, 0)
72 | XCTAssertEqual(mainVC.view.allSubviews.count, 2)
73 | XCTAssertEqual(someVC.view.allSubviews.count, 0)
74 | XCTAssertEqual(containerView.allSubviews.count, 1)
75 |
76 | someVC.view.embed {
77 | VStack {
78 | [
79 | Label("Something")
80 | ]
81 | }
82 | }
83 |
84 | XCTAssertEqual(mainVC.children.count, 1)
85 | XCTAssertEqual(someVC.children.count, 0)
86 | XCTAssertEqual(mainVC.view.allSubviews.count, 5)
87 | XCTAssertEqual(someVC.view.allSubviews.count, 3)
88 | XCTAssertEqual(containerView.allSubviews.count, 4)
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/Sources/SwiftUIKit/Views/WebView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // WebView.swift
3 | //
4 | //
5 | // Created by Zach Eriksen on 11/27/19.
6 | //
7 |
8 | import WebKit
9 |
10 | @available(iOS 9.0, *)
11 | public class WebView: WKWebView {
12 | public init() {
13 | let configuration = WKWebViewConfiguration()
14 |
15 | super.init(frame: .zero, configuration: configuration)
16 | }
17 |
18 | required init?(coder: NSCoder) {
19 | fatalError("init(coder:) has not been implemented")
20 | }
21 | }
22 |
23 | @available(iOS 9.0, *)
24 | public extension WebView {
25 | convenience init(
26 | baseURL: URL? = nil,
27 | htmlString: () -> String
28 | ) {
29 | self.init()
30 |
31 | loadHTMLString(baseURL: baseURL, htmlString: htmlString)
32 | }
33 |
34 | convenience init(url: URL) {
35 | self.init()
36 |
37 | load(url: url)
38 | }
39 |
40 | convenience init(request: URLRequest) {
41 | self.init()
42 |
43 | load(request: request)
44 | }
45 |
46 | convenience init(URL: URL, allowingReadAccessTo readAccessURL: URL) {
47 | self.init()
48 |
49 | loadFile(URL: URL, allowingReadAccessTo: URL)
50 | }
51 |
52 | convenience init(
53 | data: Data,
54 | mimeType MIMEType: String,
55 | characterEncodingName: String,
56 | baseURL: URL
57 | ) {
58 | self.init()
59 |
60 | load(data: data,
61 | mimeType: MIMEType,
62 | characterEncodingName: characterEncodingName,
63 | baseURL: baseURL)
64 | }
65 |
66 | @discardableResult
67 | func loadHTMLString(
68 | baseURL: URL? = nil,
69 | htmlString: () -> String
70 | ) -> Self {
71 | loadHTMLString(htmlString(), baseURL: baseURL)
72 |
73 | return self
74 | }
75 |
76 | @discardableResult
77 | func load(url: URL) -> Self {
78 | load(URLRequest(url: url))
79 |
80 | return self
81 | }
82 |
83 | @discardableResult
84 | func load(request: URLRequest) -> Self {
85 | load(request)
86 |
87 | return self
88 | }
89 |
90 | @discardableResult
91 | func loadFile(URL: URL, allowingReadAccessTo readAccessURL: URL) -> Self {
92 | loadFileURL(URL, allowingReadAccessTo: readAccessURL)
93 |
94 | return self
95 | }
96 |
97 | @discardableResult
98 | func load(
99 | data: Data,
100 | mimeType MIMEType: String,
101 | characterEncodingName: String,
102 | baseURL: URL
103 | ) -> Self {
104 | load(data,
105 | mimeType: MIMEType,
106 | characterEncodingName: characterEncodingName,
107 | baseURL: baseURL)
108 |
109 | return self
110 | }
111 |
112 | @discardableResult
113 | func set(uiDelegate delegate: WKUIDelegate) -> Self {
114 | uiDelegate = delegate
115 |
116 | return self
117 | }
118 |
119 | @discardableResult
120 | func set(navigationDelegate delegate: WKNavigationDelegate) -> Self {
121 | navigationDelegate = delegate
122 |
123 | return self
124 | }
125 | }
126 |
--------------------------------------------------------------------------------
/Sources/SwiftUIKit/Extensions/UIView+Chain.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIView+Chain.swift
3 | // SwiftUIKit
4 | //
5 | // Created by Zach Eriksen on 8/26/20.
6 | //
7 |
8 | import E
9 | import UIKit
10 | import Chain
11 |
12 | @available(iOS 9.0, *)
13 | private extension UIView {
14 | static func chain(
15 | link: Chain,
16 | update: @escaping (UIView) -> Void,
17 | shouldBackground: Bool = false,
18 | centeredLoadingView: UIView? = nil,
19 | embeddedLoadingView: UIView? = nil
20 | ) -> UIView {
21 | let view = UIView()
22 |
23 | if let embeddedLoadingView = embeddedLoadingView {
24 | view.embed {
25 | embeddedLoadingView
26 | }
27 | } else {
28 | view.center {
29 | centeredLoadingView ?? LoadingView().start()
30 | }
31 | }
32 |
33 | let chainEnd: Chain = .complete(
34 | .void {
35 | update(view)
36 | }
37 | )
38 |
39 | var output = Variable.void
40 |
41 | if shouldBackground {
42 | output = Chain.background(
43 | .out {
44 | link.run()
45 | },
46 | chainEnd
47 | )
48 | .run()
49 | } else {
50 | output = Chain.link(
51 | .out {
52 | link.run()
53 | },
54 | chainEnd
55 | )
56 | .run()
57 | }
58 |
59 | print("\(#function): \(output)")
60 |
61 | return view
62 | }
63 | }
64 |
65 | @available(iOS 9.0, *)
66 | public extension UIView {
67 | static func chain(
68 | link: Chain,
69 | update: @escaping (UIView) -> Void,
70 | centeredLoadingView: UIView? = nil
71 | ) -> UIView {
72 | chain(link: link,
73 | update: update,
74 | shouldBackground: false,
75 | centeredLoadingView: centeredLoadingView,
76 | embeddedLoadingView: nil)
77 | }
78 |
79 | static func chain(
80 | link: Chain,
81 | update: @escaping (UIView) -> Void,
82 | embeddedLoadingView: UIView
83 | ) -> UIView {
84 | chain(link: link,
85 | update: update,
86 | shouldBackground: false,
87 | centeredLoadingView: nil,
88 | embeddedLoadingView: embeddedLoadingView)
89 | }
90 |
91 | static func background(
92 | link: Chain,
93 | update: @escaping (UIView) -> Void,
94 | centeredLoadingView: UIView? = nil
95 | ) -> UIView {
96 | chain(link: link,
97 | update: update,
98 | shouldBackground: true,
99 | centeredLoadingView: centeredLoadingView,
100 | embeddedLoadingView: nil)
101 | }
102 |
103 | static func background(
104 | link: Chain,
105 | update: @escaping (UIView) -> Void,
106 | embeddedLoadingView: UIView
107 | ) -> UIView {
108 | chain(link: link,
109 | update: update,
110 | shouldBackground: true,
111 | centeredLoadingView: nil,
112 | embeddedLoadingView: embeddedLoadingView)
113 | }
114 | }
115 |
--------------------------------------------------------------------------------
/Sources/SwiftUIKit/Containers/HStack.swift:
--------------------------------------------------------------------------------
1 | //
2 | // HStack.swift
3 | // SwiftUIKit
4 | //
5 | // Created by Zach Eriksen on 10/29/19.
6 | //
7 |
8 | import UIKit
9 |
10 | /// Horizontal StackView
11 | @available(iOS 9.0, *)
12 | public class HStack: UIView {
13 | private var spacing: Float
14 | private var padding: Float
15 | private var alignment: UIStackView.Alignment
16 | private var distribution: UIStackView.Distribution
17 | /// The views that the HStack contains
18 | private(set) var views = [UIView]()
19 |
20 | /// Create a HStack
21 | /// - Parameters:
22 | /// - withSpacing: The amount of spacing between each child view
23 | /// - padding: The amount of space between this view and its parent view
24 | /// - alignment: The layout of arranged views perpendicular to the stack view’s axis (source: UIStackView.Alignment)
25 | /// - distribution: The layout that defines the size and position of the arranged views along the stack view’s axis (source: UIStackView.Distribution)
26 | /// - closure: A trailing closure that accepts an array of views
27 | public init(
28 | withSpacing spacing: Float = 0,
29 | padding: Float = 0,
30 | alignment: UIStackView.Alignment = .fill,
31 | distribution: UIStackView.Distribution = .fill,
32 | _ closure: () -> [UIView]
33 | ) {
34 | self.spacing = spacing
35 | self.padding = padding
36 | self.alignment = alignment
37 | self.distribution = distribution
38 | super.init(frame: .zero)
39 | views = closure()
40 | draw(views: views)
41 | }
42 |
43 | /// Create a HStack that accepts an array of UIView?
44 | /// - Parameters:
45 | /// - withSpacing: The amount of spacing between each child view
46 | /// - padding: The amount of space between this view and its parent view
47 | /// - alignment: The layout of arranged views perpendicular to the stack view’s axis (source: UIStackView.Alignment)
48 | /// - distribution: The layout that defines the size and position of the arranged views along the stack view’s axis (source: UIStackView.Distribution)
49 | /// - closure: A trailing closure that accepts an array of optional views
50 | public init(
51 | withSpacing spacing: Float = 0,
52 | padding: Float = 0,
53 | alignment: UIStackView.Alignment = .fill,
54 | distribution: UIStackView.Distribution = .fill,
55 | _ closure: () -> [UIView?]
56 | ) {
57 | self.spacing = spacing
58 | self.padding = padding
59 | self.alignment = alignment
60 | self.distribution = distribution
61 | super.init(frame: .zero)
62 | views = closure()
63 | .compactMap { $0 }
64 | draw(views: views)
65 | }
66 |
67 | required init?(coder aDecoder: NSCoder) {
68 | fatalError("init(coder:) has not been implemented")
69 | }
70 |
71 | internal func draw(views: [UIView]) {
72 | clear()
73 | .hstack(withSpacing: spacing,
74 | padding: padding,
75 | alignment: alignment,
76 | distribution: distribution)
77 | { views }
78 | }
79 |
80 | public func update(views closure: (inout [UIView]) -> Void) -> Self {
81 | closure(&views)
82 | draw(views: views)
83 |
84 | return self
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/Sources/SwiftUIKit/Containers/VStack.swift:
--------------------------------------------------------------------------------
1 | //
2 | // VStack.swift
3 | // SwiftUIKit
4 | //
5 | // Created by Zach Eriksen on 10/29/19.
6 | //
7 |
8 | import UIKit
9 |
10 | /// Vertical StackView
11 | @available(iOS 9.0, *)
12 | public class VStack: UIView {
13 | private var spacing: Float
14 | private var padding: Float
15 | private var alignment: UIStackView.Alignment
16 | private var distribution: UIStackView.Distribution
17 | /// The views that the VStack contains
18 | private(set) var views = [UIView]()
19 |
20 | /// Create a VStack
21 | /// - Parameters:
22 | /// - withSpacing: The amount of spacing between each child view
23 | /// - padding: The amount of space between this view and its parent view
24 | /// - alignment: The layout of arranged views perpendicular to the stack view’s axis (source: UIStackView.Alignment)
25 | /// - distribution: The layout that defines the size and position of the arranged views along the stack view’s axis (source: UIStackView.Distribution)
26 | /// - closure: A trailing closure that accepts an array of views
27 | public init(
28 | withSpacing spacing: Float = 0,
29 | padding: Float = 0,
30 | alignment: UIStackView.Alignment = .fill,
31 | distribution: UIStackView.Distribution = .fill,
32 | _ closure: () -> [UIView]
33 | ) {
34 | self.spacing = spacing
35 | self.padding = padding
36 | self.alignment = alignment
37 | self.distribution = distribution
38 | super.init(frame: .zero)
39 | views = closure()
40 | draw(views: views)
41 | }
42 |
43 | /// Create a VStack that accepts an array of UIView?
44 | /// - Parameters:
45 | /// - withSpacing: The amount of spacing between each child view
46 | /// - padding: The amount of space between this view and its parent view
47 | /// - alignment: The layout of arranged views perpendicular to the stack view’s axis (source: UIStackView.Alignment)
48 | /// - distribution: The layout that defines the size and position of the arranged views along the stack view’s axis (source: UIStackView.Distribution)
49 | /// - closure: A trailing closure that accepts an array of optional views
50 | public init(
51 | withSpacing spacing: Float = 0,
52 | padding: Float = 0,
53 | alignment: UIStackView.Alignment = .fill,
54 | distribution: UIStackView.Distribution = .fill,
55 | _ closure: () -> [UIView?]
56 | ) {
57 | self.spacing = spacing
58 | self.padding = padding
59 | self.alignment = alignment
60 | self.distribution = distribution
61 | super.init(frame: .zero)
62 | views = closure()
63 | .compactMap { $0 }
64 | draw(views: views)
65 | }
66 |
67 | required init?(coder aDecoder: NSCoder) {
68 | fatalError("init(coder:) has not been implemented")
69 | }
70 |
71 | internal func draw(views: [UIView]) {
72 | clear()
73 | .vstack(withSpacing: spacing,
74 | padding: padding,
75 | alignment: alignment,
76 | distribution: distribution)
77 | { views }
78 | }
79 |
80 | @discardableResult
81 | public func update(views closure: (inout [UIView]) -> Void) -> Self {
82 | closure(&views)
83 | draw(views: views)
84 |
85 | return self
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/Sources/SwiftUIKit/Views/List.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Table.swift
3 | //
4 | //
5 | // Created by Zach Eriksen on 11/2/19.
6 | //
7 |
8 | import UIKit
9 |
10 | @available(iOS 9.0, *)
11 | public class List: UITableView {
12 | private var data: [UIView]
13 | private var defaultCellHeight: Float?
14 | private var estimatedCellHeight: Float?
15 |
16 | private var didSelectHandler: ((UIView) -> Void)?
17 | private var configureCell: ((UITableViewCell) -> Void)?
18 |
19 | public init(
20 | defaultCellHeight: Float? = nil,
21 | estimatedCellHeight: Float? = nil,
22 | _ closure: () -> [UIView]
23 | ) {
24 |
25 | self.defaultCellHeight = defaultCellHeight
26 | self.estimatedCellHeight = estimatedCellHeight
27 | self.data = closure()
28 |
29 | super.init(frame: .zero, style: .plain)
30 |
31 | delegate = self
32 | dataSource = self
33 |
34 | register(UITableViewCell.self, forCellReuseIdentifier: "cell")
35 | }
36 |
37 | required init?(coder: NSCoder) {
38 | fatalError("init(coder:) has not been implemented")
39 | }
40 | }
41 |
42 | @available(iOS 9.0, *)
43 | public extension List {
44 | @discardableResult
45 | func didSelectHandler(_ action: @escaping (UIView) -> Void) -> Self {
46 | self.didSelectHandler = action
47 |
48 | return self
49 | }
50 |
51 | @discardableResult
52 | func configureCell(_ action: @escaping (UITableViewCell) -> Void) -> Self {
53 | self.configureCell = action
54 |
55 | return self
56 | }
57 | }
58 |
59 | @available(iOS 9.0, *)
60 | extension List: UITableViewDataSource {
61 | public func numberOfSections(in tableView: UITableView) -> Int {
62 | return 1
63 | }
64 |
65 | public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
66 | return data.count
67 | }
68 |
69 | public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
70 | let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
71 |
72 | configureCell?(cell)
73 | cell.contentView.clear()
74 | .embed { self.data[indexPath.row] }
75 |
76 | // Start LoadingViews
77 | if let view = cell.contentView.allSubviews.first(where: { $0 is LoadingView }),
78 | let loadingView = view as? LoadingView {
79 | loadingView.start()
80 | }
81 |
82 | return cell
83 | }
84 |
85 | public func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
86 | guard let height = defaultCellHeight else {
87 | return UITableView.automaticDimension
88 | }
89 | return CGFloat(height)
90 | }
91 |
92 | public func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
93 | guard let height = estimatedCellHeight else {
94 | return 44
95 | }
96 | return CGFloat(height)
97 | }
98 | }
99 |
100 | @available(iOS 9.0, *)
101 | extension List: UITableViewDelegate {
102 | public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
103 | didSelectHandler?(data[indexPath.row])
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/Sources/SwiftUIKit/Views/Label.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Label.swift
3 | // SwiftUIKit
4 | //
5 | // Created by Zach Eriksen on 10/29/19.
6 | //
7 |
8 | import UIKit
9 |
10 | @available(iOS 9.0, *)
11 | public class Label: UILabel {
12 |
13 | public init(_ text: String) {
14 | super.init(frame: .zero)
15 |
16 | self.text = text
17 |
18 | if #available(iOS 10.0, *) {
19 | adjustsFontForContentSizeCategory = true
20 | }
21 | accessibility(label: text, traits: .staticText)
22 | }
23 |
24 | public init(_ attributedText: AttributedString) {
25 | super.init(frame: .zero)
26 |
27 | self.attributedText = attributedText
28 |
29 | if #available(iOS 10.0, *) {
30 | adjustsFontForContentSizeCategory = true
31 | }
32 | accessibility(label: attributedText.mutableString.description, traits: .staticText)
33 | }
34 |
35 | required init?(coder: NSCoder) {
36 | fatalError("init(coder:) has not been implemented")
37 | }
38 |
39 | @discardableResult
40 | public func number(ofLines lines: Int) -> Self {
41 | numberOfLines = lines
42 |
43 | return self
44 | }
45 |
46 | @discardableResult
47 | public func font(_ font: UIFont) -> Self {
48 | self.font = font
49 |
50 | return self
51 | }
52 |
53 | @discardableResult
54 | public func font(_ textStyle: UIFont.TextStyle) -> Self {
55 | return self.font(UIFont.preferredFont(forTextStyle: textStyle))
56 | }
57 |
58 | @discardableResult
59 | public func hideIfBlank() -> Self {
60 | isHidden = text?.isEmpty ?? true
61 |
62 | return self
63 | }
64 |
65 | @discardableResult
66 | public func apply(attributes: StringAttributes) -> Self {
67 | attributedText = AttributedString(string: text ?? "", attributes: attributes)
68 |
69 | return self
70 | }
71 |
72 | @discardableResult
73 | public func apply(attributes: StringAttributes, range: ClosedRange) -> Self {
74 | attributedText = AttributedString(attributedString: attributedText ?? AttributedString(string: text ?? ""))
75 | .set(attributes: attributes, range: range)
76 |
77 | return self
78 | }
79 |
80 | @discardableResult
81 | public func text(alignment: NSTextAlignment) -> Self {
82 | textAlignment = alignment
83 |
84 | return self
85 | }
86 |
87 | @discardableResult
88 | public func text(color: UIColor) -> Self {
89 | textColor = color
90 |
91 | return self
92 | }
93 | }
94 |
95 | @available(iOS 9.0, *)
96 | public extension Label {
97 | class func title1(_ text: String) -> Label {
98 | return Label(text)
99 | .font(.title1)
100 | }
101 |
102 | class func title2(_ text: String) -> Label {
103 | return Label(text)
104 | .font(.title2)
105 | }
106 |
107 | class func title3(_ text: String) -> Label {
108 | return Label(text)
109 | .font(.title3)
110 | }
111 |
112 | class func headline(_ text: String) -> Label {
113 | return Label(text)
114 | .font(.headline)
115 | }
116 |
117 | class func subheadline(_ text: String) -> Label {
118 | return Label(text)
119 | .font(.subheadline)
120 | }
121 |
122 | class func body(_ text: String) -> Label {
123 | return Label(text)
124 | .font(.body)
125 | }
126 |
127 | class func callout(_ text: String) -> Label {
128 | return Label(text)
129 | .font(.callout)
130 | }
131 |
132 | class func caption1(_ text: String) -> Label {
133 | return Label(text)
134 | .font(.caption1)
135 | }
136 |
137 | class func caption2(_ text: String) -> Label {
138 | return Label(text)
139 | .font(.caption2)
140 | }
141 | }
142 |
--------------------------------------------------------------------------------
/Sources/SwiftUIKit/Views/LoadingImage.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LoadingImage.swift
3 | //
4 | //
5 | // Created by Zach Eriksen on 11/29/19.
6 | //
7 |
8 | import UIKit
9 |
10 | @available(iOS 9.0, *)
11 | public class LoadingImage: UIView {
12 | private var loadingTint: UIColor?
13 | private var errorHandler: ((LoadingImage?, Error?) -> Void)?
14 | private var completionHandler: ((UIImage?) -> Void)?
15 |
16 | public init(
17 | url: URL? = nil,
18 | loadingTint: UIColor? = nil,
19 | onErrorLoading: ((LoadingImage?, Error?) -> Void)? = nil,
20 | onCompletedLoading: ((UIImage?) -> Void)? = nil
21 | ) {
22 |
23 | super.init(frame: .zero)
24 |
25 | self.loadingTint = loadingTint
26 | errorHandler = onErrorLoading
27 | completionHandler = onCompletedLoading
28 |
29 | embed {
30 | LoadingView()
31 | .configure {
32 | if let tint = loadingTint {
33 | $0.color = tint
34 | }
35 | }
36 | .start()
37 | }
38 |
39 | guard let url = url else {
40 | return
41 | }
42 |
43 | load(url: url)
44 | }
45 |
46 | required init?(coder: NSCoder) {
47 | fatalError("init(coder:) has not been implemented")
48 | }
49 |
50 | @discardableResult
51 | public func contentMode(_ mode: UIView.ContentMode) -> Self {
52 | self.contentMode = mode
53 |
54 | return self
55 | }
56 |
57 | @discardableResult
58 | public func onImageLoaded(_ handler: @escaping (UIImage?) -> Void) -> Self {
59 | self.completionHandler = handler
60 |
61 | return self
62 | }
63 |
64 | @discardableResult
65 | public func load(url: URL?) -> Self {
66 | clear().embed {
67 | LoadingView()
68 | .configure {
69 | if let tint = loadingTint {
70 | $0.color = tint
71 | }
72 | }
73 | .start()
74 | }
75 |
76 | guard let url = url else {
77 | return self
78 | }
79 |
80 | let request = URLRequest(url: url)
81 | let task = URLSession.shared.dataTask(with: request) { [weak self] (data, response, error) in
82 | guard let data = data else {
83 | print("Image \(#function) Error!")
84 | print("Issue loading Image with url: \(url.absoluteString)")
85 |
86 | self?.update(color: .systemRed)
87 | self?.errorHandler?(self, error)
88 | self?.completionHandler?(nil)
89 | return
90 | }
91 | guard let image = UIImage(data: data) else {
92 | print("Image \(#function) Error!")
93 | print("Issue loading Image with url: \(url.absoluteString)")
94 | print("Error: Could not create UIImage from data")
95 |
96 | self?.update(color: .systemRed)
97 | self?.errorHandler?(self, error)
98 | self?.completionHandler?(nil)
99 | return
100 | }
101 | self?.update(image: image)
102 | self?.completionHandler?(image)
103 | }
104 |
105 | task.resume()
106 |
107 | return self
108 | }
109 |
110 | public func update(image: UIImage) {
111 | DispatchQueue.main.async { [weak self] in
112 | guard let self = self else {
113 | print("Image \(#function) Error!")
114 | print("Issue loading Image: \(image)")
115 | print("Error: Self was nil")
116 | return
117 | }
118 | self.clear()
119 | .embed {
120 | Image(image)
121 | .contentMode(self.contentMode)
122 | }
123 | }
124 | }
125 |
126 | private func update(color: UIColor) {
127 | DispatchQueue.main.async { [weak self] in
128 | guard let self = self else {
129 | print("Image \(#function) Error!")
130 | print("Issue loading Color: \(color)")
131 | print("Error: Self was nil")
132 | return
133 | }
134 | self.clear()
135 | .embed {
136 | Image(color: color)
137 | .contentMode(self.contentMode)
138 | }
139 | }
140 | }
141 | }
142 |
--------------------------------------------------------------------------------
/Tests/SwiftUIKitTests/NSAttributedString/NSMutableAttributedStringTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NSMutableAttributedStringTests.swift
3 | //
4 | //
5 | // Created by Zach Eriksen on 3/2/20.
6 | //
7 |
8 | import Foundation
9 | import XCTest
10 | @testable import SwiftUIKit
11 |
12 | @available(iOS 9.0, *)
13 | final class NSMutableAttributedStringTests: XCTestCase {
14 |
15 | func testAttributedString() {
16 | var usernameAttributes = StringAttributes(for: .font, value: UIFont.preferredFont(forTextStyle: .headline))
17 | usernameAttributes.add(key: .foregroundColor, value: UIColor.red)
18 | let captionAttributes = StringAttributes {
19 | [
20 | .font: UIFont.preferredFont(forTextStyle: .caption1),
21 | .foregroundColor: UIColor.darkGray
22 | ]
23 | }
24 | let caption = AttributedString(string: "oneleif is a project based group focused on learning and mentorship. Our core tenet of becoming skilled professionals is to work on open source projects. Open source simply means the work you are doing is available to the public. This comes with the benefit that anyone can help you on your project, and allows those without experience to see how something is made.", attributes: captionAttributes)
25 |
26 | caption.set(attributes: usernameAttributes, range: 0 ... 4)
27 |
28 | let label = Label(caption)
29 | .number(ofLines: 3)
30 |
31 | let old_usernameAttributes = [NSAttributedString.Key.font: UIFont.preferredFont(forTextStyle: .headline),
32 | NSAttributedString.Key.foregroundColor: UIColor.red]
33 | let old_captionAttributes = [NSAttributedString.Key.font: UIFont.preferredFont(forTextStyle: .caption1),
34 | NSAttributedString.Key.foregroundColor: UIColor.darkGray]
35 |
36 | let old_caption = NSMutableAttributedString(string: "oneleif is a project based group focused on learning and mentorship. Our core tenet of becoming skilled professionals is to work on open source projects. Open source simply means the work you are doing is available to the public. This comes with the benefit that anyone can help you on your project, and allows those without experience to see how something is made.", attributes: old_captionAttributes)
37 |
38 | old_caption.setAttributes(old_usernameAttributes, range: NSRange(location: 0, length: 4))
39 |
40 | let old_label = Label(old_caption)
41 | .number(ofLines: 3)
42 |
43 | XCTAssert(label.attributedText == old_label.attributedText)
44 | XCTAssert(label.text == old_label.text)
45 | XCTAssert(label.accessibilityLabel == old_label.accessibilityLabel)
46 |
47 | XCTAssert(!(label.text?.isEmpty ?? true))
48 | XCTAssert(!(label.accessibilityLabel?.isEmpty ?? true))
49 | }
50 |
51 | func testApplyLabel() {
52 | var usernameAttributes = StringAttributes(for: .font, value: UIFont.preferredFont(forTextStyle: .headline))
53 | usernameAttributes.add(key: .foregroundColor, value: UIColor.red)
54 | let captionAttributes = StringAttributes {
55 | [
56 | .font: UIFont.preferredFont(forTextStyle: .caption1),
57 | .foregroundColor: UIColor.darkGray
58 | ]
59 | }
60 | let caption = AttributedString(string: "oneleif is a project based group focused on learning and mentorship. Our core tenet of becoming skilled professionals is to work on open source projects. Open source simply means the work you are doing is available to the public. This comes with the benefit that anyone can help you on your project, and allows those without experience to see how something is made.", attributes: captionAttributes)
61 |
62 | caption.set(attributes: usernameAttributes, range: 0 ... 4)
63 |
64 | let initLabel = Label(caption)
65 | .number(ofLines: 3)
66 | let applyLabel = Label("oneleif is a project based group focused on learning and mentorship. Our core tenet of becoming skilled professionals is to work on open source projects. Open source simply means the work you are doing is available to the public. This comes with the benefit that anyone can help you on your project, and allows those without experience to see how something is made.")
67 | .apply(attributes: captionAttributes)
68 | .apply(attributes: usernameAttributes, range: 0 ... 4)
69 |
70 | XCTAssert(initLabel.attributedText == applyLabel.attributedText)
71 | XCTAssert(initLabel.text == applyLabel.text)
72 | XCTAssert(initLabel.accessibilityLabel == applyLabel.accessibilityLabel)
73 |
74 | XCTAssert(!(applyLabel.text?.isEmpty ?? true))
75 | XCTAssert(!(applyLabel.accessibilityLabel?.isEmpty ?? true))
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | We as members, contributors, and leaders pledge to make participation in our
6 | community a harassment-free experience for everyone, regardless of age, body
7 | size, visible or invisible disability, ethnicity, sex characteristics, gender
8 | identity and expression, level of experience, education, socio-economic status,
9 | nationality, personal appearance, race, religion, or sexual identity
10 | and orientation.
11 |
12 | We pledge to act and interact in ways that contribute to an open, welcoming,
13 | diverse, inclusive, and healthy community.
14 |
15 | ## Our Standards
16 |
17 | Examples of behavior that contributes to a positive environment for our
18 | community include:
19 |
20 | * Demonstrating empathy and kindness toward other people
21 | * Being respectful of differing opinions, viewpoints, and experiences
22 | * Giving and gracefully accepting constructive feedback
23 | * Accepting responsibility and apologizing to those affected by our mistakes,
24 | and learning from the experience
25 | * Focusing on what is best not just for us as individuals, but for the
26 | overall community
27 |
28 | Examples of unacceptable behavior include:
29 |
30 | * The use of sexualized language or imagery, and sexual attention or
31 | advances of any kind
32 | * Trolling, insulting or derogatory comments, and personal or political attacks
33 | * Public or private harassment
34 | * Publishing others' private information, such as a physical or email
35 | address, without their explicit permission
36 | * Other conduct which could reasonably be considered inappropriate in a
37 | professional setting
38 |
39 | ## Enforcement Responsibilities
40 |
41 | Community leaders are responsible for clarifying and enforcing our standards of
42 | acceptable behavior and will take appropriate and fair corrective action in
43 | response to any behavior that they deem inappropriate, threatening, offensive,
44 | or harmful.
45 |
46 | Community leaders have the right and responsibility to remove, edit, or reject
47 | comments, commits, code, wiki edits, issues, and other contributions that are
48 | not aligned to this Code of Conduct, and will communicate reasons for moderation
49 | decisions when appropriate.
50 |
51 | ## Scope
52 |
53 | This Code of Conduct applies within all community spaces, and also applies when
54 | an individual is officially representing the community in public spaces.
55 | Examples of representing our community include using an official e-mail address,
56 | posting via an official social media account, or acting as an appointed
57 | representative at an online or offline event.
58 |
59 | ## Enforcement
60 |
61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
62 | reported to the community leaders responsible for enforcement at
63 | oneleif.
64 | All complaints will be reviewed and investigated promptly and fairly.
65 |
66 | All community leaders are obligated to respect the privacy and security of the
67 | reporter of any incident.
68 |
69 | ## Enforcement Guidelines
70 |
71 | Community leaders will follow these Community Impact Guidelines in determining
72 | the consequences for any action they deem in violation of this Code of Conduct:
73 |
74 | ### 1. Correction
75 |
76 | **Community Impact**: Use of inappropriate language or other behavior deemed
77 | unprofessional or unwelcome in the community.
78 |
79 | **Consequence**: A private, written warning from community leaders, providing
80 | clarity around the nature of the violation and an explanation of why the
81 | behavior was inappropriate. A public apology may be requested.
82 |
83 | ### 2. Warning
84 |
85 | **Community Impact**: A violation through a single incident or series
86 | of actions.
87 |
88 | **Consequence**: A warning with consequences for continued behavior. No
89 | interaction with the people involved, including unsolicited interaction with
90 | those enforcing the Code of Conduct, for a specified period of time. This
91 | includes avoiding interactions in community spaces as well as external channels
92 | like social media. Violating these terms may lead to a temporary or
93 | permanent ban.
94 |
95 | ### 3. Temporary Ban
96 |
97 | **Community Impact**: A serious violation of community standards, including
98 | sustained inappropriate behavior.
99 |
100 | **Consequence**: A temporary ban from any sort of interaction or public
101 | communication with the community for a specified period of time. No public or
102 | private interaction with the people involved, including unsolicited interaction
103 | with those enforcing the Code of Conduct, is allowed during this period.
104 | Violating these terms may lead to a permanent ban.
105 |
106 | ### 4. Permanent Ban
107 |
108 | **Community Impact**: Demonstrating a pattern of violation of community
109 | standards, including sustained inappropriate behavior, harassment of an
110 | individual, or aggression toward or disparagement of classes of individuals.
111 |
112 | **Consequence**: A permanent ban from any sort of public interaction within
113 | the community.
114 |
115 | ## Attribution
116 |
117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage],
118 | version 2.0, available at
119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
120 |
121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct
122 | enforcement ladder](https://github.com/mozilla/diversity).
123 |
124 | [homepage]: https://www.contributor-covenant.org
125 |
126 | For answers to common questions about this code of conduct, see the FAQ at
127 | https://www.contributor-covenant.org/faq. Translations are available at
128 | https://www.contributor-covenant.org/translations.
129 |
--------------------------------------------------------------------------------
/documentation/Navigate.md:
--------------------------------------------------------------------------------
1 | # Navigate
2 |
3 | ``` swift
4 | @available(iOS 9.0, *) public class Navigate
5 | ```
6 |
7 | ## Initializers
8 |
9 | ### `init(controller:)`
10 |
11 | ``` swift
12 | public init(controller: UINavigationController? = nil)
13 | ```
14 |
15 | ## Properties
16 |
17 | ### `shared`
18 |
19 | ``` swift
20 | var shared: Navigate
21 | ```
22 |
23 | ## Methods
24 |
25 | ### `configure(controller:)`
26 |
27 | Configure the Navigate Singleton with the Root Navigation Controller
28 |
29 | ``` swift
30 | @discardableResult public func configure(controller: UINavigationController?) -> Self
31 | ```
32 |
33 | ### `set(title:)`
34 |
35 | Set the visibleViewController's title
36 |
37 | ``` swift
38 | @discardableResult public func set(title: String) -> Self
39 | ```
40 |
41 | #### Parameters
42 |
43 | - title: The title of the currentViewController
44 |
45 | ### `setLeft(barButton:animated:)`
46 |
47 | Set the left barButton
48 |
49 | ``` swift
50 | @discardableResult public func setLeft(barButton: UIBarButtonItem?, animated: Bool = true) -> Self
51 | ```
52 |
53 | #### Parameters
54 |
55 | - barButton: The UIBarButtonItem to be set
56 | - animated: Should animate setting the left UIBarButtonItem
57 |
58 | ### `setRight(barButton:animated:)`
59 |
60 | Set the right barButton
61 |
62 | ``` swift
63 | @discardableResult public func setRight(barButton: UIBarButtonItem?, animated: Bool = true) -> Self
64 | ```
65 |
66 | #### Parameters
67 |
68 | - barButton: The UIBarButtonItem to be set
69 | - animated: Should animate setting the right UIBarButtonItem
70 |
71 | ### `setLeft(barButtons:animated:)`
72 |
73 | Set the left barButtons
74 |
75 | ``` swift
76 | @discardableResult public func setLeft(barButtons: [UIBarButtonItem]?, animated: Bool = true) -> Self
77 | ```
78 |
79 | #### Parameters
80 |
81 | - barButton: The \[UIBarButtonItem\] to be set
82 | - animated: Should animate setting the left \[UIBarButtonItem\]
83 |
84 | ### `setRight(barButtons:animated:)`
85 |
86 | Set the right barButtons
87 |
88 | ``` swift
89 | @discardableResult public func setRight(barButtons: [UIBarButtonItem]?, animated: Bool = true) -> Self
90 | ```
91 |
92 | #### Parameters
93 |
94 | - barButton: The \[UIBarButtonItem\] to be set
95 | - animated: Should animate setting the right \[UIBarButtonItem\]
96 |
97 | ### `go(_:style:completion:)`
98 |
99 | Go to a viewController by using the configured NavigationController
100 |
101 | ``` swift
102 | public func go(_ viewController: UIViewController, style: NavigationStyle, completion: (() -> Void)? = nil)
103 | ```
104 |
105 | #### Parameters
106 |
107 | - viewController: UIViewController to navigate to
108 | - style: Style of navigation
109 |
110 | ### `go(from:to:style:completion:)`
111 |
112 | Go to a viewController by using another viewController
113 |
114 | ``` swift
115 | public func go(from: UIViewController, to: UIViewController, style: NavigationStyle, completion: (() -> Void)? = nil)
116 | ```
117 |
118 | #### Parameters
119 |
120 | - from: The UIViewController that is handling the navigation
121 | - viewController: UIViewController to navigate to
122 | - style: Style of navigation
123 |
124 | ### `back(toRoot:)`
125 |
126 | Navigate back and dismiss the visibleViewController
127 |
128 | ``` swift
129 | public func back(toRoot: Bool = false)
130 | ```
131 |
132 | #### Parameters
133 |
134 | - toRoot: Should navigate back to the rootViewController
135 |
136 | ### `dismiss()`
137 |
138 | Dismiss the visibleViewController
139 |
140 | ``` swift
141 | public func dismiss()
142 | ```
143 |
144 | ### `alert(title:message:withActions:secondsToPersist:_:)`
145 |
146 | Show an Alert
147 |
148 | ``` swift
149 | public func alert(title: String, message: String, withActions actions: [UIAlertAction] = [], secondsToPersist: Double?, _ closure: ((UIAlertController) -> Void)? = nil)
150 | ```
151 |
152 | #### Parameters
153 |
154 | - title: Title of the UIAlertController
155 | - message: Message of the UIAlertController
156 | - withactions: Array of action objects to be added to the Alert
157 | - secondsToPersist: Amount of seconds the Alert should show before dismissing itself
158 | - closure: A closure that is passed the UIAlertController before presenting it
159 |
160 | ### `actionSheet(title:message:withActions:_:)`
161 |
162 | Show an ActionSheet
163 |
164 | ``` swift
165 | public func actionSheet(title: String, message: String, withActions actions: [UIAlertAction] = [], _ closure: ((UIAlertController) -> Void)? = nil)
166 | ```
167 |
168 | #### Parameters
169 |
170 | - title: Title of the UIAlertController
171 | - message: Message of the UIAlertController
172 | - withactions: Array of action objects to be added to the ActionSheet
173 | - closure: A closure that is passed the UIAlertController before presenting it
174 |
175 | ### `toast(style:pinToTop:secondsToPersist:animationInDuration:animationOutDuration:padding:tapHandler:_:)`
176 |
177 | Show a Toast Message
178 |
179 | ``` swift
180 | @available(iOS 11.0, *) public func toast(style: ToastStyle = .custom, pinToTop: Bool = true, secondsToPersist: Double? = nil, animationInDuration: Double = 0.5, animationOutDuration: Double = 0.5, padding: Float = 8, tapHandler: @escaping (UIView) -> Void = { $0.removeFromSuperview() }, _ closure: @escaping () -> UIView)
181 | ```
182 |
183 | #### Parameters
184 |
185 | - style: The ToastStyle (default: .custom)
186 | - pinToTop: Should the Toast pin to the top or bottom (default: true)
187 | - secondsToPersist: Amount of seconds the Toast should show before dismissing itself
188 | - animationInDuration: The amount of seconds the Toast should fade in (default: 0.5)
189 | - animationOutDuration: The amount of seconds the Toast should fade out (default: 0.5)
190 | - padding: The amount of spacing around the Toast
191 | - tapHandler: What happens when the user taps on the Toast (default: { $0.removeFromSuperview() })
192 | - closure: A trailing closure that accepts a view
193 |
194 | ### `destroyToast()`
195 |
196 | Destory the toast
197 |
198 | ``` swift
199 | public func destroyToast()
200 | ```
201 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # SwiftUIKit
2 |
3 |
4 | *UIKit code that is fun to write.*
5 |
6 | ## [Documentation (WIP)](documentation/Home.md)
7 |
8 | [**Getting Started**](https://medium.com/oneleif/an-intro-to-swiftuikit-6acd9d4c94ec)
9 |
10 | [**Basic Examples**](https://github.com/0xLeif/Basic_SwiftUIKit_Examples)
11 |
12 | ## Introduction
13 |
14 | SwiftUIKit is mainly based off of two functions, embed and stack. Embedding a view inside another view is exactly what we did in the first two examples. Now we can add another to the view, but then we have to manage the constraints for the subviews! An easy way to handle this is to use UIStackViews, so in SwiftUIKit there are VStack, HStack, and ZStack. UIStackViews manage the constraints for you and do just as the name suggests, stack views you give it in the order you give them.
15 |
16 | ## Example Code
17 | ```Swift
18 | import UIKit
19 | import SwiftUIKit
20 |
21 | class ViewController: UIViewController {
22 |
23 | override func viewDidLoad() {
24 | super.viewDidLoad()
25 |
26 | Navigate.shared.configure(controller: navigationController)
27 | .set(title: "Hello SwiftUIKit")
28 | .setRight(barButton: BarButton {
29 | Button("Button 0") {
30 | print("Tapped the barbutton")
31 | }
32 | })
33 |
34 |
35 | view.embed {
36 | SafeAreaView {
37 | List(defaultCellHeight: 60) {
38 | [
39 | Button("Say Hello") {
40 | print("Hello World!")
41 | },
42 |
43 | HStack(withSpacing: 8) {
44 | [
45 | Label("Name"),
46 |
47 | Divider(.vertical),
48 |
49 | Spacer(),
50 |
51 | TextField(value: "SwiftUIKit",
52 | placeholder: "Some Name",
53 | keyboardType: .default)
54 | .inputHandler { print("New Name: \($0)") }
55 | ]
56 | },
57 |
58 | Label.callout("This is some callout text!"),
59 |
60 | ZStack {
61 | [
62 | Image(.blue)
63 | .frame(height: 60, width: 60)
64 | .offset(x: 100)
65 | ]
66 | },
67 |
68 | NavButton(destination: {
69 | UIViewController {
70 | UIView(backgroundColor: .white) {
71 | LoadingImage(URL(string: "https://cdn11.bigcommerce.com/s-oe2q4reh/images/stencil/2048x2048/products/832/1401/Beige_Pekingese_Puppy__21677.1568609759.jpg")!)
72 | .contentMode(.scaleAspectFit)
73 | }
74 | }
75 | }, style: .push) {
76 | Label("Go see a puppy")
77 | },
78 |
79 | Button("Show an Alert") {
80 | Navigate.shared.alert(title: "Hello this is an Alert!",
81 | message: "Just a test...",
82 | secondsToPersist: 3)
83 | },
84 |
85 | Button("Show an Alert w/ cancel") {
86 | Navigate.shared.alert(title: "Hello World",
87 | message: "This is an alert",
88 | withActions: [.cancel],
89 | secondsToPersist: 3)
90 | },
91 |
92 | Button("Show a Toast Message") {
93 | Navigate.shared.toast(style: .error, pinToTop: true, secondsToPersist: 4) {
94 | Label("This is a test error message!")
95 | }
96 | }
97 | ]
98 | }
99 | }
100 | }
101 | }
102 | }
103 | ```
104 |
105 | ## Example View
106 |
107 | 
108 |
109 | ## GitHub Supporters
110 |
111 | [
](https://github.com/suzyfendrick)
112 |
113 |
114 | ****
115 |
116 | ## oneleif project
117 | This means that the project is sponsored by the oneleif community, and the collaborators are team members from oneleif.
118 |
119 | 
120 |
121 |
122 |
123 | [](https://twitter.com/oneleifdev)
124 |
125 | [](https://www.youtube.com/channel/UC3HN0jID38K0Vb_WChvgQmA)
126 |
127 | ## What is oneleif?
128 | oneleif is a nonprofit community that supports tech minded individuals. We do this by offering a fun loving community that works on Open Sourced projects together.
129 | We love to give back through free resources and guidance.
130 |
131 | ## How to join oneleif
132 | Click on the link below to join the Discord server.
133 |
134 | [](https://discord.gg/tv9UdJK)
135 |
136 | -OR-
137 |
138 | [Check out our website](http://oneleif.com)
139 |
140 |
141 | ### Questions?
142 | Feel free to email us at: oneleifdev@gmail.com
143 |
144 | -OR-
145 |
146 | Ask questions in the discord
147 |
--------------------------------------------------------------------------------
/Tests/SwiftUIKitTests/TableView/TableViewTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TableViewTests.swift
3 | // SwiftUIKitTests
4 | //
5 | // Created by Zach Eriksen on 4/12/20.
6 | //
7 |
8 | import XCTest
9 | import UIKit
10 | @testable import SwiftUIKit
11 |
12 | @available(iOS 11.0, *)
13 | class TableViewTests: XCTestCase {
14 |
15 | func testTableViewNoCells() {
16 | let table = TableView()
17 |
18 | table.register(cells: [])
19 |
20 | table
21 | .append {
22 | [
23 | [
24 | TableTestHelper.InfoData(title: "First Info!", count: 01, bio: "This is the very first!"),
25 | TableTestHelper.InfoData(title: "Second!", count: 3, bio: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris dignissim bibendum mi, non posuere risus imperdiet vitae. Vestibulum sed magna nec nunc finibus tempus quis nec sapien. Etiam non faucibus turpis. Ut diam libero, porttitor mollis leo sed, venenatis varius eros. Etiam ut sollicitudin massa. Integer consequat laoreet dui at tincidunt. Donec eget lacinia ligula. Aliquam eu maximus magna."),
26 | TableTestHelper.InfoData(title: "2nd Section!", count: 2222, bio: "2")
27 | ]
28 |
29 | ]
30 | }
31 |
32 | XCTAssertEqual(table.sections(), 1)
33 | XCTAssertEqual(table.rows(forSection: 0), 3)
34 | }
35 |
36 | func testTableViewAppend() {
37 | let table = TableView()
38 |
39 | table.register(cells: [
40 | TableTestHelper.InfoCell.self,
41 | ])
42 |
43 | table
44 | .append {
45 | [
46 | [
47 | TableTestHelper.InfoData(title: "First Info!", count: 01, bio: "This is the very first!"),
48 | TableTestHelper.InfoData(title: "Second!", count: 3, bio: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris dignissim bibendum mi, non posuere risus imperdiet vitae. Vestibulum sed magna nec nunc finibus tempus quis nec sapien. Etiam non faucibus turpis. Ut diam libero, porttitor mollis leo sed, venenatis varius eros. Etiam ut sollicitudin massa. Integer consequat laoreet dui at tincidunt. Donec eget lacinia ligula. Aliquam eu maximus magna."),
49 | TableTestHelper.InfoData(title: "2nd Section!", count: 2222, bio: "2")
50 | ]
51 |
52 | ]
53 | }
54 |
55 | XCTAssertEqual(table.sections(), 1)
56 | XCTAssertEqual(table.rows(forSection: 0), 3)
57 | }
58 |
59 | func testTableViewUpdate() {
60 | let table = TableView()
61 |
62 | table.register(cells: [
63 | TableTestHelper.InfoCell.self,
64 | ])
65 |
66 | table.append {
67 | [
68 | [TableTestHelper.InfoData(title: "First Info!", count: 01, bio: "This is the very first!")]
69 | ]
70 | }
71 |
72 | table
73 | .update { data in
74 | [
75 | [
76 | TableTestHelper.InfoData(title: "Second!", count: 3, bio: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris dignissim bibendum mi, non posuere risus imperdiet vitae. Vestibulum sed magna nec nunc finibus tempus quis nec sapien. Etiam non faucibus turpis. Ut diam libero, porttitor mollis leo sed, venenatis varius eros. Etiam ut sollicitudin massa. Integer consequat laoreet dui at tincidunt. Donec eget lacinia ligula. Aliquam eu maximus magna."),
77 | TableTestHelper.InfoData(title: "2nd Section!", count: 2222, bio: "2")
78 | ]
79 |
80 | ] +
81 | data
82 | }
83 |
84 | XCTAssertEqual(table.sections(), 2)
85 | XCTAssertEqual(table.rows(forSection: 0), 2)
86 | XCTAssertEqual(table.rows(forSection: 1), 1)
87 | }
88 |
89 | func testTableViewUpdate_OneSection() {
90 | let table = TableView()
91 |
92 | table.register(cells: [
93 | TableTestHelper.InfoCell.self,
94 | ])
95 |
96 | table.append {
97 | [
98 | [TableTestHelper.InfoData(title: "First Info!", count: 01, bio: "This is the very first!")]
99 | ]
100 | }
101 |
102 | table
103 | .update { data in
104 | var data = data
105 |
106 | data[0] += [
107 | TableTestHelper.InfoData(title: "Second!", count: 3, bio: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris dignissim bibendum mi, non posuere risus imperdiet vitae. Vestibulum sed magna nec nunc finibus tempus quis nec sapien. Etiam non faucibus turpis. Ut diam libero, porttitor mollis leo sed, venenatis varius eros. Etiam ut sollicitudin massa. Integer consequat laoreet dui at tincidunt. Donec eget lacinia ligula. Aliquam eu maximus magna."),
108 | TableTestHelper.InfoData(title: "2nd Section!", count: 2222, bio: "2")
109 | ]
110 |
111 | return data
112 | }
113 |
114 | XCTAssertEqual(table.sections(), 1)
115 | XCTAssertEqual(table.rows(forSection: 0), 3)
116 | }
117 | }
118 |
119 | @available(iOS 11.0, *)
120 | fileprivate class TableTestHelper {
121 | struct InfoData {
122 | let title: String
123 | let count: Int
124 | let bio: String
125 | }
126 |
127 | class InfoCell: TableViewCell {
128 | let label: Label = Label("")
129 | let detailLabel: Label = Label("")
130 | let bioLabel: Label = Label("")
131 | }
132 | }
133 |
134 | @available(iOS 11.0, *)
135 | extension TableTestHelper.InfoData: CellDisplayable {
136 | var cellID: String {
137 | TableTestHelper.InfoCell.ID
138 | }
139 | }
140 |
141 | @available(iOS 11.0, *)
142 | extension TableTestHelper.InfoCell {
143 | static var ID: String {
144 | "InfoCell"
145 | }
146 |
147 | func update(forData data: CellDisplayable) {
148 | guard let data = data as? TableTestHelper.InfoData else {
149 | return
150 | }
151 |
152 | label.text = "SomeCell! \(data.title)"
153 | detailLabel.text = "\(data.count)"
154 | bioLabel.text = data.bio
155 | }
156 |
157 | func configure(forData data: CellDisplayable) {
158 | guard contentView.allSubviews.count == 0 else {
159 | return
160 | }
161 |
162 | contentView
163 | .clear()
164 | .embed {
165 | VStack {
166 | [
167 | HStack {
168 | [
169 | label,
170 | Spacer(),
171 | detailLabel
172 | ]
173 | }
174 | .padding(16),
175 | bioLabel
176 | .number(ofLines: 5)
177 | ]
178 | }
179 | }
180 | }
181 | }
182 |
--------------------------------------------------------------------------------
/Tests/SwiftUIKitTests/Case/Extensions/CALayer+SwiftUIKitTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIView+SwiftUIKitTests.swift
3 | // SwiftUIKitTests
4 | //
5 | // Created by Oskar on 06/03/2020.
6 | //
7 |
8 | import XCTest
9 | @testable import SwiftUIKit
10 |
11 | @available(iOS 9.0, *)
12 | class CALayer_SwiftUIKitTests: XCTestCase {
13 | private var label: Label!
14 | private let testColor: UIColor? = UIColor.red
15 | private let testFloat: Float = 0.5
16 | private let testCGRect = CGRect(x: 10.0, y: 20.0, width: 200.0, height: 200.0)
17 | private let testKey = "test"
18 |
19 | override func setUp() {
20 | super.setUp()
21 | }
22 |
23 | override func tearDown() {
24 | label = nil
25 | super.tearDown()
26 | }
27 |
28 | func setUpLabel(testing name: String) -> Label {
29 | label = Label("Testing: \(name)")
30 | return label
31 | }
32 |
33 | func testView_layerModifier_borderColor() {
34 | setUpLabel(testing: "borderColor")
35 | .layer(borderColor: testColor)
36 |
37 | XCTAssertEqual(label.layer.borderColor, testColor?.cgColor)
38 | }
39 |
40 | func testView_layerModifier_backgroundColor() {
41 | setUpLabel(testing: "backgroundColor")
42 | .layer(backgroundColor: testColor)
43 |
44 | XCTAssertEqual(label.layer.backgroundColor, testColor?.cgColor)
45 | }
46 |
47 | func testView_layerModifier_borderWidth() {
48 | setUpLabel(testing: "borderWidth")
49 | .layer(borderWidth: testFloat)
50 |
51 | XCTAssertEqual(label.layer.borderWidth, CGFloat(testFloat))
52 | }
53 |
54 | func testView_layerModifier_opacity() {
55 | setUpLabel(testing: "opacity")
56 | .layer(opacity: testFloat)
57 |
58 | XCTAssertEqual(label.layer.opacity, Float(testFloat))
59 | }
60 |
61 | func testView_layerModifier_contentsGravity() {
62 | let testGravity: CALayerContentsGravity = .center
63 |
64 | setUpLabel(testing: "contentsGravity")
65 | .layer(contentsGravity: testGravity)
66 |
67 | XCTAssertEqual(label.layer.contentsGravity, testGravity)
68 | }
69 |
70 | func testView_layerModifier_isHidden() {
71 | setUpLabel(testing: "layer's isHidden")
72 | .layer(isHidden: true)
73 |
74 | XCTAssertTrue(label.layer.isHidden)
75 | }
76 |
77 | func testView_layerModifier_masksToBounds() {
78 | setUpLabel(testing: "masksToBounds")
79 | .layer(masksToBounds: true)
80 |
81 | XCTAssertTrue(label.layer.masksToBounds)
82 | }
83 |
84 | func testView_layerModifier_mask() {
85 | let testMask = CALayer()
86 |
87 | setUpLabel(testing: "mask")
88 | .layer(mask: testMask)
89 |
90 | XCTAssertEqual(label.layer.mask, testMask)
91 | }
92 |
93 | func testView_layerModifier_doubleSided() {
94 | setUpLabel(testing: "isDoubleSided")
95 | .layer(isDoubleSided: true)
96 |
97 | XCTAssertTrue(label.layer.isDoubleSided)
98 | }
99 |
100 | @available(iOS 11.0, *)
101 | func testView_layerModifier_maskedCorners() {
102 | let testCornerMask = CACornerMask()
103 |
104 | setUpLabel(testing: "maskedCorners")
105 | .layer(maskedCorners: testCornerMask)
106 |
107 | XCTAssertEqual(label.layer.maskedCorners, testCornerMask)
108 | }
109 |
110 | func testView_layerModifier_shadowOpacity() {
111 | setUpLabel(testing: "shadowOpacity")
112 | .layer(shadowOpacity: testFloat)
113 |
114 | XCTAssertEqual(label.layer.shadowOpacity, testFloat)
115 | }
116 |
117 | func testView_layerModifier_shadowRadius() {
118 | setUpLabel(testing: "shadowRadius")
119 | .layer(shadowRadius: testFloat)
120 |
121 | XCTAssertEqual(label.layer.shadowRadius, CGFloat(testFloat))
122 | }
123 |
124 | func testView_layerModifier_shadowOffset() {
125 | let testShadowOffset = CGSize(width: 200, height: 200)
126 |
127 | setUpLabel(testing: "shadowOffset")
128 | .layer(shadowOffset: testShadowOffset)
129 |
130 | XCTAssertEqual(label.layer.shadowOffset, testShadowOffset)
131 | }
132 |
133 | func testView_layerModifier_shadowPath() {
134 | let testPath = CGPath(roundedRect: .infinite, cornerWidth: 2, cornerHeight: 5, transform: nil)
135 |
136 | setUpLabel(testing: "shadowPath")
137 | .layer(shadowPath: testPath)
138 |
139 | XCTAssertEqual(label.layer.shadowPath, testPath)
140 | }
141 |
142 |
143 | func testView_layerModifier_allowsEdgeAntialiasing() {
144 | setUpLabel(testing: "allowsEdgeAntialiasing")
145 | .layer(allowsEdgeAntialiasing: true)
146 |
147 | XCTAssertTrue(label.layer.allowsEdgeAntialiasing)
148 | }
149 |
150 | func testView_layerModifier_allowsGroupOpacity() {
151 | setUpLabel(testing: "allowsGroupOpacity")
152 | .layer(allowsGroupOpacity: true)
153 |
154 | XCTAssertTrue(label.layer.allowsGroupOpacity)
155 | }
156 |
157 | func testView_layerModifier_style() {
158 | let testDict = [0: "Test"]
159 |
160 | setUpLabel(testing: "style")
161 | .layer(style: testDict)
162 |
163 | XCTAssertEqual(label.layer.style?.first?.value as? String,
164 | testDict[0])
165 | }
166 |
167 | func testView_layerModifier_filter() {
168 | let testFilters = [CIFilter(name: "CIGaussianBlur")]
169 |
170 | setUpLabel(testing: "Filters")
171 | .layer(filters: testFilters as [Any])
172 |
173 | let layers = label.layer.filters as! [CIFilter]
174 | XCTAssertEqual(layers, testFilters)
175 | }
176 |
177 | func testView_layerModifier_addAnimation() {
178 | let testAnimation = CABasicAnimation(keyPath: "addAnimation")
179 | testAnimation.beginTime = CACurrentMediaTime() + 0.2
180 | testAnimation.duration = 0.5
181 | testAnimation.fromValue = 0.0
182 | testAnimation.toValue = 1.0
183 |
184 | setUpLabel(testing: "opacity")
185 | .layer(addAnimation: testAnimation, forKey: testKey)
186 |
187 | XCTAssertEqual(label.layer.animation(forKey: testKey)?.beginTime,
188 | (testAnimation as CABasicAnimation?)?.beginTime)
189 | }
190 |
191 | func testView_layerModifier_removeAnimation() {
192 | let testAnimation = CABasicAnimation(keyPath: "opacity")
193 | testAnimation.beginTime = CACurrentMediaTime() + 0.2
194 | testAnimation.duration = 0.5
195 | testAnimation.fromValue = 0.0
196 | testAnimation.toValue = 1.0
197 |
198 | setUpLabel(testing: "removingAnimation")
199 | .layer(addAnimation: testAnimation, forKey: testKey)
200 | .layer(removeAnimationForKey: testKey)
201 |
202 | XCTAssertNil(label.layer.animation(forKey: "test"))
203 | }
204 |
205 | func testView_layerModifier_removeAllAnimations() {
206 | let testAnimation = CABasicAnimation(keyPath: "opacity")
207 | testAnimation.beginTime = CACurrentMediaTime() + 0.2
208 | testAnimation.duration = 0.5
209 | testAnimation.fromValue = 0.0
210 | testAnimation.toValue = 1.0
211 |
212 | setUpLabel(testing: "removeAllAnimations")
213 | .layer(addAnimation: testAnimation, forKey: testKey)
214 | .removeAllAnimationsFromLayer()
215 |
216 | XCTAssertNil(label.layer.animationKeys())
217 | }
218 | }
219 |
--------------------------------------------------------------------------------
/Sources/SwiftUIKit/Extensions/CALayer+SwiftUIKit.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CALayer+SwiftUIKit.swift
3 | // SwiftUIKit
4 | //
5 | // Created by Oskar on 08/03/2020.
6 | //
7 |
8 | import UIKit
9 |
10 | @available(iOS 9.0, *)
11 | public extension UIView {
12 |
13 | /// Change layer's background color
14 | /// - Parameter color: You can use `UIColor.colorName.cgColor` to pass UIColor value
15 | @discardableResult
16 | func layer(backgroundColor color: CGColor?) -> Self {
17 | layer.backgroundColor = color
18 |
19 | return self
20 | }
21 |
22 | /// Change layer's background color
23 | @discardableResult
24 | func layer(backgroundColor color: UIColor?) -> Self {
25 | layer.backgroundColor = color?.cgColor
26 |
27 | return self
28 | }
29 |
30 | /// Change layer's content's gravity.
31 | /// - Parameter gravity: Will be used as layer's gravity.
32 | @discardableResult
33 | func layer(contentsGravity: CALayerContentsGravity) -> Self {
34 | layer.contentsGravity = contentsGravity
35 |
36 | return self
37 | }
38 |
39 | /// Change layer's corner radius.
40 | /// - Parameter radius: value, defines corner radius.
41 | @discardableResult
42 | func layer(cornerRadius: Float) -> Self {
43 | layer.cornerRadius = CGFloat(cornerRadius)
44 |
45 | return self
46 | }
47 |
48 | /// Change layer's border color.
49 | /// - Parameter color: use `UIColor.colorName.cgColor` to pass UIColor value.
50 | @discardableResult
51 | func layer(borderColor: CGColor?) -> Self {
52 | layer.borderColor = borderColor
53 |
54 | return self
55 | }
56 |
57 | /// Change layer's border color.
58 | @discardableResult
59 | func layer(borderColor: UIColor?) -> Self {
60 | layer.borderColor = borderColor?.cgColor
61 |
62 | return self
63 | }
64 |
65 | /// Change layer's border width.
66 | /// - Parameter width: Will be used as width value.
67 | @discardableResult
68 | func layer(borderWidth: Float) -> Self {
69 | layer.borderWidth = CGFloat(borderWidth)
70 | return self
71 | }
72 |
73 | /// Change layer's opacity
74 | /// - Parameter value: Will be used as opacity value.
75 | @discardableResult
76 | func layer(opacity: Float) -> Self {
77 | layer.opacity = opacity
78 |
79 | return self
80 | }
81 |
82 | /// Change layer's isHidden value
83 | @discardableResult
84 | func layer(isHidden: Bool) -> Self {
85 | layer.isHidden = isHidden
86 |
87 | return self
88 | }
89 |
90 | /// Set masks to bounds value.
91 | @discardableResult
92 | func layer(masksToBounds: Bool) -> Self {
93 | layer.masksToBounds = masksToBounds
94 |
95 | return self
96 | }
97 |
98 | /// Set layer's mask.
99 | /// - Parameter layer: Allows to set existing type or create new one inside closure.
100 | @discardableResult
101 | func layer(mask: @autoclosure () -> CALayer?) -> Self {
102 | self.layer.mask = mask()
103 |
104 | return self
105 | }
106 |
107 | /// Set `isDoubleSided` parameter.
108 | @discardableResult
109 | func layer(isDoubleSided: Bool) -> Self {
110 | layer.isDoubleSided = isDoubleSided
111 |
112 | return self
113 | }
114 |
115 | /// Set masked corners value.
116 | /// - Parameter corners: You can pass exisiting value or create it inside closure
117 | @available(iOS 11.0, *)
118 | @discardableResult
119 | func layer(maskedCorners: @autoclosure () -> CACornerMask) -> Self {
120 | layer.maskedCorners = maskedCorners()
121 |
122 | return self
123 | }
124 |
125 | /// Set layer's shadow opacity.
126 | /// - Parameter opacity:
127 | @discardableResult
128 | func layer(shadowOpacity: Float) -> Self {
129 | layer.shadowOpacity = shadowOpacity
130 |
131 | return self
132 | }
133 |
134 | /// Change layer's shadow color.
135 | /// - Parameter color: Pass `UIColor.colorName.cgColor` to pass UIColor value
136 | @discardableResult
137 | func layer(shadowColor: CGColor?) -> Self {
138 | layer.shadowColor = shadowColor
139 |
140 | return self
141 | }
142 |
143 | /// Change layer's shadow color.
144 | @discardableResult
145 | func layer(shadowColor: UIColor?) -> Self {
146 | layer.shadowColor = shadowColor?.cgColor
147 |
148 | return self
149 | }
150 |
151 | /// Set layer's shadow radius. Takes `Float` and converts it to `CGFloat` for programmer's convenience
152 | /// - Parameter radius:
153 | @discardableResult
154 | func layer(shadowRadius: Float) -> Self {
155 | layer.shadowRadius = CGFloat(shadowRadius)
156 |
157 | return self
158 | }
159 |
160 | /// Changes layer's shadowOffset to given in parameter
161 | /// - Parameter offset: offset value, can be calculated using closure inside or just add ready one.
162 | @discardableResult
163 | func layer(shadowOffset: @autoclosure () -> CGSize) -> Self {
164 | layer.shadowOffset = shadowOffset()
165 |
166 | return self
167 | }
168 |
169 | /// Set shadow path by passing existing object or use curly braces to create own one.
170 | /// - Parameter path:
171 | @discardableResult
172 | func layer(shadowPath: @autoclosure () -> CGPath?) -> Self {
173 | layer.shadowPath = shadowPath()
174 |
175 | return self
176 | }
177 |
178 | /// Set layer's `allowsEdgeAntialiasing`.
179 | @discardableResult
180 | func layer(allowsEdgeAntialiasing: Bool) -> Self {
181 | layer.allowsEdgeAntialiasing = allowsEdgeAntialiasing
182 |
183 | return self
184 | }
185 |
186 | /// Set layer's `allowsGroupOpacity`.
187 | @discardableResult
188 | func layer(allowsGroupOpacity: Bool) -> Self {
189 | layer.allowsGroupOpacity = allowsGroupOpacity
190 |
191 | return self
192 | }
193 |
194 | /// Set layer's style.
195 | /// - Parameter style: You can pass exisiting value or create new one inside closure.
196 | @discardableResult
197 | func layer(style: @autoclosure () -> [AnyHashable: Any]?) -> Self {
198 | layer.style = style()
199 |
200 | return self
201 | }
202 |
203 | /// Set layer's filters.
204 | /// - Parameter filters: You can pass exisiting value or create new one inside closure.
205 | @discardableResult
206 | func layer(filters: @autoclosure () -> [Any]?) -> Self {
207 | layer.filters = filters()
208 |
209 | return self
210 | }
211 |
212 | /// Set layer's `isOpaque` Boolean.
213 | @discardableResult
214 | func layer(isOpaque: Bool) -> Self {
215 | layer.isOpaque = isOpaque
216 |
217 | return self
218 | }
219 |
220 | /// Set layer's `drawsAsynchronously`
221 | @discardableResult
222 | func layer(drawsAsynchronously: Bool) -> Self {
223 | layer.drawsAsynchronously = drawsAsynchronously
224 |
225 | return self
226 | }
227 |
228 | /// Add animation to a layer.
229 | /// - Parameters:
230 | /// - key: Key which allows to identify given animation.
231 | /// - animation: You can pass existing value or create it inside closure.
232 | @discardableResult
233 | func layer(addAnimation animation: CAAnimation, forKey key: String?) -> Self {
234 | layer.add(animation, forKey: key)
235 |
236 | return self
237 | }
238 |
239 | /// Remove layer's animation specified by given key.
240 | /// - Parameter key: Key that's assigned to animation.
241 | @discardableResult
242 | func layer(removeAnimationForKey key: String) -> Self {
243 | layer.removeAnimation(forKey: key)
244 |
245 | return self
246 | }
247 |
248 | /// Remove all existing layer's animations.
249 | @discardableResult
250 | func removeAllAnimationsFromLayer() -> Self {
251 | layer.removeAllAnimations()
252 |
253 | return self
254 | }
255 |
256 | /// Modify the object's layer
257 | /// - Parameters:
258 | /// - closure: A trailing closure that receives itself.layer inside the closue
259 | @discardableResult
260 | func layer(_ closure: (CALayer) -> Void) -> Self {
261 | closure(layer)
262 |
263 | return self
264 | }
265 | }
266 |
--------------------------------------------------------------------------------
/documentation/Map.md:
--------------------------------------------------------------------------------
1 | # Map
2 |
3 | ``` swift
4 | public class Map: MKMapView
5 | ```
6 |
7 | ## Inheritance
8 |
9 | `MKMapView`, `MKMapViewDelegate`
10 |
11 | ## Initializers
12 |
13 | ### `init(lat:lon:points:)`
14 |
15 | ``` swift
16 | public init(lat latitude: Double, lon longitude: Double, points: (() -> [MapPoint])? = nil)
17 | ```
18 |
19 | ### `init(region:points:)`
20 |
21 | ``` swift
22 | convenience init(region: MKCoordinateRegion, points: (() -> [MapPoint])? = nil)
23 | ```
24 |
25 | ## Methods
26 |
27 | ### `type(_:)`
28 |
29 | ``` swift
30 | @discardableResult func type(_ type: MKMapType) -> Self
31 | ```
32 |
33 | ### `zoomEnabled(_:)`
34 |
35 | ``` swift
36 | @discardableResult func zoomEnabled(_ value: Bool = true) -> Self
37 | ```
38 |
39 | ### `scrollEnabled(_:)`
40 |
41 | ``` swift
42 | @discardableResult func scrollEnabled(_ value: Bool = true) -> Self
43 | ```
44 |
45 | ### `pitchEnabled(_:)`
46 |
47 | ``` swift
48 | @discardableResult func pitchEnabled(_ value: Bool = true) -> Self
49 | ```
50 |
51 | ### `rotateEnabled(_:)`
52 |
53 | ``` swift
54 | @discardableResult func rotateEnabled(_ value: Bool = true) -> Self
55 | ```
56 |
57 | ### `delegate(_:)`
58 |
59 | Note: If delegate isn't its own class, modifiers based on delegate's methods will do nothing.
60 |
61 | ``` swift
62 | @discardableResult func delegate(_ delegate: MKMapViewDelegate?) -> Self
63 | ```
64 |
65 | ### `zoom(_:)`
66 |
67 | ``` swift
68 | @discardableResult func zoom(_ multiplier: Double) -> Self
69 | ```
70 |
71 | ### `visible(rect:animate:edgePadding:)`
72 |
73 | ``` swift
74 | @discardableResult func visible(rect: MKMapRect, animate: Bool = true, edgePadding: UIEdgeInsets? = nil) -> Self
75 | ```
76 |
77 | ### `move(to:animate:)`
78 |
79 | Changes coordinates and span.
80 |
81 | ``` swift
82 | @discardableResult func move(to region: MKCoordinateRegion, animate: Bool = true) -> Self
83 | ```
84 |
85 | ### `move(to:animate:)`
86 |
87 | Changes only coordinates.
88 |
89 | ``` swift
90 | @discardableResult func move(to coordinates: CLLocationCoordinate2D, animate: Bool = true) -> Self
91 | ```
92 |
93 | ### `center(_:animated:)`
94 |
95 | ``` swift
96 | @discardableResult func center(_ center: CLLocationCoordinate2D, animated: Bool = true) -> Self
97 | ```
98 |
99 | ### `show(annotations:animated:)`
100 |
101 | ``` swift
102 | @discardableResult func show(annotations: [MKAnnotation], animated: Bool = true) -> Self
103 | ```
104 |
105 | ### `show(annotations:animated:)`
106 |
107 | ``` swift
108 | @discardableResult func show(annotations: MKAnnotation, animated: Bool = true) -> Self
109 | ```
110 |
111 | ### `camera(boundary:animated:)`
112 |
113 | ``` swift
114 | @discardableResult func camera(boundary: MKMapView.CameraBoundary?, animated: Bool = true) -> Self
115 | ```
116 |
117 | ### `set(cameraZoomRange:animated:)`
118 |
119 | ``` swift
120 | @discardableResult func set(cameraZoomRange: MKMapView.CameraZoomRange?, animated: Bool) -> Self
121 | ```
122 |
123 | ### `camera(_:animated:)`
124 |
125 | ``` swift
126 | @discardableResult func camera(_ camera: MKMapCamera, animated: Bool = true) -> Self
127 | ```
128 |
129 | ### `showBuildings(_:)`
130 |
131 | ``` swift
132 | @discardableResult func showBuildings(_ bool: Bool) -> Self
133 | ```
134 |
135 | ### `showCompass(_:)`
136 |
137 | ``` swift
138 | @discardableResult func showCompass(_ bool: Bool) -> Self
139 | ```
140 |
141 | ### `showScale(_:)`
142 |
143 | ``` swift
144 | @discardableResult func showScale(_ bool: Bool) -> Self
145 | ```
146 |
147 | ### `showTraffic(_:)`
148 |
149 | ``` swift
150 | @discardableResult func showTraffic(_ bool: Bool) -> Self
151 | ```
152 |
153 | ### `pointOfInterestFilter(filter:)`
154 |
155 | ``` swift
156 | @discardableResult func pointOfInterestFilter(filter: MKPointOfInterestFilter?) -> Self
157 | ```
158 |
159 | ### `showUserLocation(_:)`
160 |
161 | ``` swift
162 | @discardableResult func showUserLocation(_ bool: Bool) -> Self
163 | ```
164 |
165 | ### `user(trackingMode:animated:)`
166 |
167 | ``` swift
168 | @discardableResult func user(trackingMode: MKUserTrackingMode, animated: Bool = true) -> Self
169 | ```
170 |
171 | ### `select(annotation:animated:)`
172 |
173 | ``` swift
174 | @discardableResult func select(annotation: MKAnnotation, animated: Bool = true) -> Self
175 | ```
176 |
177 | ### `deselect(annotation:animated:)`
178 |
179 | ``` swift
180 | @discardableResult func deselect(annotation: MKAnnotation, animated: Bool = true) -> Self
181 | ```
182 |
183 | ### `remove(annotation:)`
184 |
185 | ``` swift
186 | @discardableResult func remove(annotation: MKAnnotation) -> Self
187 | ```
188 |
189 | ### `remove(annotations:)`
190 |
191 | ``` swift
192 | @discardableResult func remove(annotations: [MKAnnotation]) -> Self
193 | ```
194 |
195 | ### `add(annotation:)`
196 |
197 | ``` swift
198 | @discardableResult func add(annotation: MKAnnotation) -> Self
199 | ```
200 |
201 | ### `add(point:)`
202 |
203 | ``` swift
204 | @discardableResult func add(point: MapPoint) -> Self
205 | ```
206 |
207 | ### `add(annotations:)`
208 |
209 | ``` swift
210 | @discardableResult func add(annotations: [MKAnnotation]) -> Self
211 | ```
212 |
213 | ### `add(points:)`
214 |
215 | ``` swift
216 | @discardableResult func add(points: [MapPoint]) -> Self
217 | ```
218 |
219 | ### `register(classes:)`
220 |
221 | ``` swift
222 | @discardableResult func register(classes: [String: AnyClass.Type]) -> Self
223 | ```
224 |
225 | ### `fitTo(region:)`
226 |
227 | ``` swift
228 | @discardableResult func fitTo(region: MKCoordinateRegion) -> Self
229 | ```
230 |
231 | ### `fitTo(rect:edgePadding:)`
232 |
233 | ``` swift
234 | @discardableResult func fitTo(rect: MKMapRect, edgePadding: UIEdgeInsets? = nil) -> Self
235 | ```
236 |
237 | ### `onFinishLoading(_:)`
238 |
239 | ``` swift
240 | @discardableResult func onFinishLoading(_ handler: @escaping (MKMapView) -> ()) -> Self
241 | ```
242 |
243 | ### `afterRegionChange(_:)`
244 |
245 | ``` swift
246 | @discardableResult func afterRegionChange(_ handler: @escaping (MKMapView) -> ()) -> Self
247 | ```
248 |
249 | ### `beforeRegionChange(_:)`
250 |
251 | ``` swift
252 | @discardableResult func beforeRegionChange(_ handler: @escaping (MKMapView) -> ()) -> Self
253 | ```
254 |
255 | ### `configure(identifier:_:)`
256 |
257 | ``` swift
258 | @discardableResult func configure(identifier: String?, _ annotationView: @escaping ((MKAnnotationView?, MKAnnotation) -> (MKAnnotationView?))) -> Self
259 | ```
260 |
261 | ### `onAccessoryTap(_:)`
262 |
263 | ``` swift
264 | @discardableResult func onAccessoryTap(_ handler: @escaping (MKMapView, MKAnnotationView, UIControl) -> ()) -> Self
265 | ```
266 |
267 | ### `onAnnotationViewStateChange(_:)`
268 |
269 | ``` swift
270 | @discardableResult func onAnnotationViewStateChange(_ handler: @escaping ((MKMapView, MKAnnotationView, MKAnnotationView.DragState, MKAnnotationView.DragState) -> ())) -> Self
271 | ```
272 |
273 | ### `onAnnotationSelect(_:)`
274 |
275 | ``` swift
276 | @discardableResult func onAnnotationSelect(_ handler: @escaping ((MKMapView, MKAnnotationView) -> ())) -> Self
277 | ```
278 |
279 | ### `onAnnotationDeselect(_:)`
280 |
281 | ``` swift
282 | @discardableResult func onAnnotationDeselect(_ handler: @escaping ((MKMapView, MKAnnotationView) -> ())) -> Self
283 | ```
284 |
285 | ### `mapViewDidFinishLoadingMap(_:)`
286 |
287 | ``` swift
288 | public func mapViewDidFinishLoadingMap(_ mapView: MKMapView)
289 | ```
290 |
291 | ### `mapView(_:regionDidChangeAnimated:)`
292 |
293 | ``` swift
294 | public func mapView(_ mapView: MKMapView, regionDidChangeAnimated animated: Bool)
295 | ```
296 |
297 | ### `mapView(_:regionWillChangeAnimated:)`
298 |
299 | ``` swift
300 | public func mapView(_ mapView: MKMapView, regionWillChangeAnimated animated: Bool)
301 | ```
302 |
303 | ### `mapView(_:viewFor:)`
304 |
305 | ``` swift
306 | public func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView?
307 | ```
308 |
309 | ### `mapView(_:annotationView:calloutAccessoryControlTapped:)`
310 |
311 | ``` swift
312 | public func mapView(_ mapView: MKMapView, annotationView view: MKAnnotationView, calloutAccessoryControlTapped control: UIControl)
313 | ```
314 |
315 | ### `mapView(_:annotationView:didChange:fromOldState:)`
316 |
317 | ``` swift
318 | public func mapView(_ mapView: MKMapView, annotationView view: MKAnnotationView, didChange newState: MKAnnotationView.DragState, fromOldState oldState: MKAnnotationView.DragState)
319 | ```
320 |
321 | ### `mapView(_:didSelect:)`
322 |
323 | ``` swift
324 | public func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView)
325 | ```
326 |
327 | ### `mapView(_:didDeselect:)`
328 |
329 | ``` swift
330 | public func mapView(_ mapView: MKMapView, didDeselect view: MKAnnotationView)
331 | ```
332 |
--------------------------------------------------------------------------------
/documentation/TableView.md:
--------------------------------------------------------------------------------
1 | # TableView
2 |
3 | ``` swift
4 | @available(iOS 11.0, *) public class TableView: UITableView
5 | ```
6 |
7 | ## Inheritance
8 |
9 | `UITableView`, `UITableViewDataSource`, `UITableViewDelegate`
10 |
11 | ## Initializers
12 |
13 | ### `init(initalData:style:)`
14 |
15 | ``` swift
16 | public init(initalData: [[CellDisplayable]] = [[CellDisplayable]](), style: UITableView.Style = .plain)
17 | ```
18 |
19 | ## Properties
20 |
21 | ### `data`
22 |
23 | ``` swift
24 | var data: [[CellDisplayable]]
25 | ```
26 |
27 | ## Methods
28 |
29 | ### `update(shouldReloadData:_:)`
30 |
31 | ``` swift
32 | @discardableResult func update(shouldReloadData: Bool = false, _ closure: ([[CellDisplayable]]) -> [[CellDisplayable]]) -> Self
33 | ```
34 |
35 | ### `append(shouldReloadData:_:)`
36 |
37 | ``` swift
38 | @discardableResult func append(shouldReloadData: Bool = false, _ closure: () -> [[CellDisplayable]]) -> Self
39 | ```
40 |
41 | ### `numberOfSections(in:)`
42 |
43 | ``` swift
44 | public func numberOfSections(in tableView: UITableView) -> Int
45 | ```
46 |
47 | ### `tableView(_:numberOfRowsInSection:)`
48 |
49 | ``` swift
50 | public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
51 | ```
52 |
53 | ### `tableView(_:indentationLevelForRowAt:)`
54 |
55 | ``` swift
56 | public func tableView(_ tableView: UITableView, indentationLevelForRowAt indexPath: IndexPath) -> Int
57 | ```
58 |
59 | ### `tableView(_:cellForRowAt:)`
60 |
61 | ``` swift
62 | public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
63 | ```
64 |
65 | ### `tableView(_:heightForHeaderInSection:)`
66 |
67 | ``` swift
68 | public func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat
69 | ```
70 |
71 | ### `tableView(_:heightForFooterInSection:)`
72 |
73 | ``` swift
74 | public func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat
75 | ```
76 |
77 | ### `tableView(_:viewForHeaderInSection:)`
78 |
79 | ``` swift
80 | public func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView?
81 | ```
82 |
83 | ### `tableView(_:viewForFooterInSection:)`
84 |
85 | ``` swift
86 | public func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView?
87 | ```
88 |
89 | ### `tableView(_:canEditRowAt:)`
90 |
91 | ``` swift
92 | public func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool
93 | ```
94 |
95 | ### `tableView(_:canMoveRowAt:)`
96 |
97 | ``` swift
98 | public func tableView(_ tableView: UITableView, canMoveRowAt indexPath: IndexPath) -> Bool
99 | ```
100 |
101 | ### `tableView(_:canFocusRowAt:)`
102 |
103 | ``` swift
104 | public func tableView(_ tableView: UITableView, canFocusRowAt indexPath: IndexPath) -> Bool
105 | ```
106 |
107 | ### `tableView(_:shouldHighlightRowAt:)`
108 |
109 | ``` swift
110 | public func tableView(_ tableView: UITableView, shouldHighlightRowAt indexPath: IndexPath) -> Bool
111 | ```
112 |
113 | ### `tableView(_:shouldIndentWhileEditingRowAt:)`
114 |
115 | ``` swift
116 | public func tableView(_ tableView: UITableView, shouldIndentWhileEditingRowAt indexPath: IndexPath) -> Bool
117 | ```
118 |
119 | ### `tableView(_:shouldShowMenuForRowAt:)`
120 |
121 | ``` swift
122 | public func tableView(_ tableView: UITableView, shouldShowMenuForRowAt indexPath: IndexPath) -> Bool
123 | ```
124 |
125 | ### `tableView(_:editingStyleForRowAt:)`
126 |
127 | ``` swift
128 | public func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCell.EditingStyle
129 | ```
130 |
131 | ### `tableView(_:titleForDeleteConfirmationButtonForRowAt:)`
132 |
133 | ``` swift
134 | public func tableView(_ tableView: UITableView, titleForDeleteConfirmationButtonForRowAt indexPath: IndexPath) -> String?
135 | ```
136 |
137 | ### `tableView(_:editActionsForRowAt:)`
138 |
139 | ``` swift
140 | public func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]?
141 | ```
142 |
143 | ### `tableView(_:commit:forRowAt:)`
144 |
145 | ``` swift
146 | public func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath)
147 | ```
148 |
149 | ### `tableView(_:didSelectRowAt:)`
150 |
151 | ``` swift
152 | public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
153 | ```
154 |
155 | ### `tableView(_:didDeselectRowAt:)`
156 |
157 | ``` swift
158 | public func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath)
159 | ```
160 |
161 | ### `tableView(_:willBeginEditingRowAt:)`
162 |
163 | ``` swift
164 | public func tableView(_ tableView: UITableView, willBeginEditingRowAt indexPath: IndexPath)
165 | ```
166 |
167 | ### `tableView(_:didEndEditingRowAt:)`
168 |
169 | ``` swift
170 | public func tableView(_ tableView: UITableView, didEndEditingRowAt indexPath: IndexPath?)
171 | ```
172 |
173 | ### `tableView(_:didHighlightRowAt:)`
174 |
175 | ``` swift
176 | public func tableView(_ tableView: UITableView, didHighlightRowAt indexPath: IndexPath)
177 | ```
178 |
179 | ### `tableView(_:didUnhighlightRowAt:)`
180 |
181 | ``` swift
182 | public func tableView(_ tableView: UITableView, didUnhighlightRowAt indexPath: IndexPath)
183 | ```
184 |
185 | ### `tableView(_:moveRowAt:to:)`
186 |
187 | ``` swift
188 | public func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath)
189 | ```
190 |
191 | ### `tableView(_:leadingSwipeActionsConfigurationForRowAt:)`
192 |
193 | ``` swift
194 | @available(iOS 11.0, *) public func tableView(_ tableView: UITableView, leadingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration?
195 | ```
196 |
197 | ### `tableView(_:trailingSwipeActionsConfigurationForRowAt:)`
198 |
199 | ``` swift
200 | @available(iOS 11.0, *) public func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration?
201 | ```
202 |
203 | ### `set(dataSource:)`
204 |
205 | ``` swift
206 | @discardableResult func set(dataSource: UITableViewDataSource) -> Self
207 | ```
208 |
209 | ### `set(delegate:)`
210 |
211 | ``` swift
212 | @discardableResult func set(delegate: UITableViewDelegate) -> Self
213 | ```
214 |
215 | ### `register(cells:)`
216 |
217 | ``` swift
218 | @discardableResult func register(cells: [TableViewCell.Type]) -> Self
219 | ```
220 |
221 | ### `headerView(_:)`
222 |
223 | ``` swift
224 | @discardableResult func headerView(_ handler: @escaping TableHeaderFooterViewHandler) -> Self
225 | ```
226 |
227 | ### `footerView(_:)`
228 |
229 | ``` swift
230 | @discardableResult func footerView(_ handler: @escaping TableHeaderFooterViewHandler) -> Self
231 | ```
232 |
233 | ### `headerHeight(_:)`
234 |
235 | ``` swift
236 | @discardableResult func headerHeight(_ handler: @escaping (Int) -> CGFloat) -> Self
237 | ```
238 |
239 | ### `footerHeight(_:)`
240 |
241 | ``` swift
242 | @discardableResult func footerHeight(_ handler: @escaping (Int) -> CGFloat) -> Self
243 | ```
244 |
245 | ### `indentationLevelForRowAtIndexPath(_:)`
246 |
247 | ``` swift
248 | @discardableResult func indentationLevelForRowAtIndexPath(_ handler: @escaping (IndexPath) -> Int) -> Self
249 | ```
250 |
251 | ### `canEditRowAtIndexPath(_:)`
252 |
253 | ``` swift
254 | @discardableResult func canEditRowAtIndexPath(_ handler: @escaping (IndexPath) -> Bool) -> Self
255 | ```
256 |
257 | ### `canMoveRowAtIndexPath(_:)`
258 |
259 | ``` swift
260 | @discardableResult func canMoveRowAtIndexPath(_ handler: @escaping (IndexPath) -> Bool) -> Self
261 | ```
262 |
263 | ### `canFocusRowAtIndexPath(_:)`
264 |
265 | ``` swift
266 | @discardableResult func canFocusRowAtIndexPath(_ handler: @escaping (IndexPath) -> Bool) -> Self
267 | ```
268 |
269 | ### `shouldHighlightRow(_:)`
270 |
271 | ``` swift
272 | @discardableResult func shouldHighlightRow(_ handler: @escaping TableHighlightIndexPathHandler) -> Self
273 | ```
274 |
275 | ### `shouldIndentWhileEditingRowAtIndexPath(_:)`
276 |
277 | ``` swift
278 | @discardableResult func shouldIndentWhileEditingRowAtIndexPath(_ handler: @escaping (IndexPath) -> Bool) -> Self
279 | ```
280 |
281 | ### `shouldShowMenuForRowAtIndexPath(_:)`
282 |
283 | ``` swift
284 | @discardableResult func shouldShowMenuForRowAtIndexPath(_ handler: @escaping (IndexPath) -> Bool) -> Self
285 | ```
286 |
287 | ### `editingStyleForRowAtIndexPath(_:)`
288 |
289 | ``` swift
290 | @discardableResult func editingStyleForRowAtIndexPath(_ handler: @escaping (IndexPath) -> UITableViewCell.EditingStyle) -> Self
291 | ```
292 |
293 | ### `titleForDeleteConfirmationButtonForRowAtIndexPath(_:)`
294 |
295 | ``` swift
296 | @discardableResult func titleForDeleteConfirmationButtonForRowAtIndexPath(_ handler: @escaping (IndexPath) -> String) -> Self
297 | ```
298 |
299 | ### `editActionsForRowAtIndexPath(_:)`
300 |
301 | ``` swift
302 | @discardableResult func editActionsForRowAtIndexPath(_ handler: @escaping (IndexPath) -> [UITableViewRowAction]) -> Self
303 | ```
304 |
305 | ### `commitEditingStyleForRowAtIndexPath(_:)`
306 |
307 | ``` swift
308 | @discardableResult func commitEditingStyleForRowAtIndexPath(_ handler: @escaping (UITableViewCell.EditingStyle, IndexPath) -> Void) -> Self
309 | ```
310 |
311 | ### `didSelectRow(_:)`
312 |
313 | ``` swift
314 | @discardableResult func didSelectRow(_ handler: @escaping TableDidSelectIndexPathHandler) -> Self
315 | ```
316 |
317 | ### `didDeselectRowAtIndexPath(_:)`
318 |
319 | ``` swift
320 | @discardableResult func didDeselectRowAtIndexPath(_ handler: @escaping (IndexPath) -> Void) -> Self
321 | ```
322 |
323 | ### `willBeginEditingRowAtIndexPath(_:)`
324 |
325 | ``` swift
326 | @discardableResult func willBeginEditingRowAtIndexPath(_ handler: @escaping (IndexPath) -> Void) -> Self
327 | ```
328 |
329 | ### `didEndEditingRowAtIndexPath(_:)`
330 |
331 | ``` swift
332 | @discardableResult func didEndEditingRowAtIndexPath(_ handler: @escaping (IndexPath?) -> Void) -> Self
333 | ```
334 |
335 | ### `didHighlightRowAtIndexPath(_:)`
336 |
337 | ``` swift
338 | @discardableResult func didHighlightRowAtIndexPath(_ handler: @escaping (IndexPath) -> Void) -> Self
339 | ```
340 |
341 | ### `didUnhighlightRowAtIndexPath(_:)`
342 |
343 | ``` swift
344 | @discardableResult func didUnhighlightRowAtIndexPath(_ handler: @escaping (IndexPath) -> Void) -> Self
345 | ```
346 |
347 | ### `moveRowAtSourceIndexPathToDestinationIndexPath(_:)`
348 |
349 | ``` swift
350 | @discardableResult func moveRowAtSourceIndexPathToDestinationIndexPath(_ handler: @escaping (IndexPath, IndexPath) -> Void) -> Self
351 | ```
352 |
353 | ### `leadingSwipeActionsConfigurationForRowAtIndexPath(_:)`
354 |
355 | ``` swift
356 | @discardableResult func leadingSwipeActionsConfigurationForRowAtIndexPath(_ handler: @escaping (IndexPath) -> UISwipeActionsConfiguration) -> Self
357 | ```
358 |
359 | ### `trailingSwipeActionsConfigurationForRowAtIndexPath(_:)`
360 |
361 | ``` swift
362 | @discardableResult func trailingSwipeActionsConfigurationForRowAtIndexPath(_ handler: @escaping (IndexPath) -> UISwipeActionsConfiguration) -> Self
363 | ```
364 |
--------------------------------------------------------------------------------
/Sources/SwiftUIKit/Navigation/Navigate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Navigate.swift
3 | //
4 | //
5 | // Created by Zach Eriksen on 11/4/19.
6 | //
7 |
8 | import UIKit
9 |
10 | @available(iOS 9.0, *)
11 | public class Navigate {
12 | public enum NavigationStyle {
13 | case push
14 | case modal
15 | }
16 |
17 | public enum ToastStyle {
18 | case error
19 | case warning
20 | case success
21 | case info
22 | case debug
23 | case custom
24 |
25 | public var color: UIColor {
26 | switch self {
27 | case .error:
28 | return .systemRed
29 | case .warning:
30 | return .systemYellow
31 | case .success:
32 | return .systemGreen
33 | case .info:
34 | return .systemBlue
35 | case .debug:
36 | return .systemGray
37 | case .custom:
38 | return .clear
39 | }
40 | }
41 | }
42 |
43 | private var navigationController: UINavigationController?
44 | private var toast: UIView?
45 | private var didTapToastHandler: ((UIView) -> Void)?
46 |
47 | public static var shared: Navigate = Navigate()
48 | public init(controller: UINavigationController? = nil) {
49 | configure(controller: controller)
50 | }
51 |
52 | // MARK: Configure NavigationController
53 |
54 | /// Configure the Navigate Singleton with the Root Navigation Controller
55 | @discardableResult
56 | public func configure(controller: UINavigationController?) -> Self {
57 | self.navigationController = controller
58 |
59 | return self
60 | }
61 |
62 | /// Set the visibleViewController's title
63 | /// - Parameters:
64 | /// - title: The title of the currentViewController
65 | @discardableResult
66 | public func set(title: String) -> Self {
67 | navigationController?.visibleViewController?.title = title
68 |
69 | return self
70 | }
71 |
72 | /// Set the left barButton
73 | /// - Parameters:
74 | /// - barButton: The UIBarButtonItem to be set
75 | /// - animated: Should animate setting the left UIBarButtonItem
76 | @discardableResult
77 | public func setLeft(barButton: UIBarButtonItem?, animated: Bool = true) -> Self {
78 | navigationController?.visibleViewController?.navigationItem.setLeftBarButton(barButton, animated: animated)
79 |
80 | return self
81 | }
82 |
83 | /// Set the right barButton
84 | /// - Parameters:
85 | /// - barButton: The UIBarButtonItem to be set
86 | /// - animated: Should animate setting the right UIBarButtonItem
87 | @discardableResult
88 | public func setRight(barButton: UIBarButtonItem?, animated: Bool = true) -> Self {
89 | navigationController?.visibleViewController?.navigationItem.setRightBarButton(barButton, animated: animated)
90 |
91 | return self
92 | }
93 |
94 | /// Set the left barButtons
95 | /// - Parameters:
96 | /// - barButton: The [UIBarButtonItem] to be set
97 | /// - animated: Should animate setting the left [UIBarButtonItem]
98 | @discardableResult
99 | public func setLeft(barButtons: [UIBarButtonItem]?, animated: Bool = true) -> Self {
100 | navigationController?.visibleViewController?.navigationItem.setLeftBarButtonItems(barButtons, animated: animated)
101 |
102 | return self
103 | }
104 |
105 | /// Set the right barButtons
106 | /// - Parameters:
107 | /// - barButton: The [UIBarButtonItem] to be set
108 | /// - animated: Should animate setting the right [UIBarButtonItem]
109 | @discardableResult
110 | public func setRight(barButtons: [UIBarButtonItem]?, animated: Bool = true) -> Self {
111 | navigationController?.visibleViewController?.navigationItem.setRightBarButtonItems(barButtons, animated: animated)
112 |
113 | return self
114 | }
115 |
116 | // MARK: Navigation
117 |
118 | /// Go to a viewController by using the configured NavigationController
119 | /// - Parameters:
120 | /// - viewController: UIViewController to navigate to
121 | /// - style: Style of navigation
122 | public func go(
123 | _ viewController: UIViewController,
124 | style: NavigationStyle,
125 | completion: (() -> Void)? = nil
126 | ) {
127 |
128 | guard let controller = navigationController else {
129 | print("Navigate \(#function) Error!")
130 | print("Issue trying to navigate to \(viewController)")
131 | print("Error: Could not unwrap navigationController")
132 | return
133 | }
134 |
135 | switch style {
136 | case .push:
137 | controller.show(viewController, sender: self)
138 | case .modal:
139 | controller.present(viewController, animated: true, completion: completion)
140 | }
141 | }
142 |
143 | /// Go to a viewController by using another viewController
144 | /// - Parameters:
145 | /// - from: The UIViewController that is handling the navigation
146 | /// - viewController: UIViewController to navigate to
147 | /// - style: Style of navigation
148 | public func go(
149 | from: UIViewController,
150 | to: UIViewController,
151 | style: NavigationStyle,
152 | completion: (() -> Void)? = nil
153 | ) {
154 |
155 |
156 | switch style {
157 | case .push:
158 | from.show(to, sender: self)
159 | case .modal:
160 | from.present(to, animated: true, completion: completion)
161 | }
162 | }
163 |
164 | /// Navigate back and dismiss the visibleViewController
165 | /// - Parameters:
166 | /// - toRoot: Should navigate back to the rootViewController
167 | public func back(toRoot: Bool = false) {
168 | guard let controller = navigationController else {
169 | print("Navigate \(#function) Error!")
170 | print("Issue trying to navigate back")
171 | print("Error: Could not unwrap navigationController")
172 | return
173 | }
174 |
175 | dismiss()
176 |
177 | if toRoot {
178 | controller.popToRootViewController(animated: true)
179 | }
180 |
181 | controller.popViewController(animated: true)
182 | }
183 |
184 | /// Dismiss the visibleViewController
185 | public func dismiss() {
186 | guard let controller = navigationController else {
187 | print("Navigate \(#function) Error!")
188 | print("Issue trying to dismiss presentingViewController")
189 | print("Error: Could not unwrap navigationController")
190 | return
191 | }
192 |
193 | if let presentingVC = controller.visibleViewController {
194 | presentingVC.dismiss(animated: true)
195 | }
196 | }
197 |
198 | // MARK: Alert
199 |
200 | /// Show an Alert
201 | /// - Parameters:
202 | /// - title: Title of the UIAlertController
203 | /// - message: Message of the UIAlertController
204 | /// - withactions: Array of action objects to be added to the Alert
205 | /// - secondsToPersist: Amount of seconds the Alert should show before dismissing itself
206 | /// - closure: A closure that is passed the UIAlertController before presenting it
207 | public func alert(
208 | title: String,
209 | message: String,
210 | withActions actions: [UIAlertAction] = [],
211 | secondsToPersist: Double?,
212 | _ closure: ((UIAlertController) -> Void)? = nil
213 | ) {
214 |
215 | let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
216 |
217 | actions.forEach { alert.addAction($0) }
218 |
219 | closure?(alert)
220 |
221 | if let timeToLive = secondsToPersist {
222 | DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + timeToLive) {
223 | if alert.isFirstResponder {
224 | alert.dismiss(animated: true)
225 | }
226 | }
227 | }
228 |
229 | go(alert, style: .modal)
230 | }
231 |
232 | // MARK: ActionSheet
233 |
234 | /// Show an ActionSheet
235 | /// - Parameters:
236 | /// - title: Title of the UIAlertController
237 | /// - message: Message of the UIAlertController
238 | /// - withactions: Array of action objects to be added to the ActionSheet
239 | /// - closure: A closure that is passed the UIAlertController before presenting it
240 | public func actionSheet(
241 | title: String,
242 | message: String,
243 | withActions actions: [UIAlertAction] = [],
244 | _ closure: ((UIAlertController) -> Void)? = nil
245 | ) {
246 |
247 | let actionSheet = UIAlertController(title: title, message: message, preferredStyle: .actionSheet)
248 |
249 | actions.forEach { actionSheet.addAction($0) }
250 |
251 | closure?(actionSheet)
252 |
253 | go(actionSheet, style: .modal)
254 | }
255 |
256 | // MARK: Toasts & Messages
257 |
258 | /// Show a Toast Message
259 | /// - Parameters:
260 | /// - style: The ToastStyle (default: .custom)
261 | /// - pinToTop: Should the Toast pin to the top or bottom (default: true)
262 | /// - secondsToPersist: Amount of seconds the Toast should show before dismissing itself
263 | /// - animationInDuration: The amount of seconds the Toast should fade in (default: 0.5)
264 | /// - animationOutDuration: The amount of seconds the Toast should fade out (default: 0.5)
265 | /// - padding: The amount of spacing around the Toast
266 | /// - tapHandler: What happens when the user taps on the Toast (default: { $0.removeFromSuperview() })
267 | /// - closure: A trailing closure that accepts a view
268 | @available(iOS 11.0, *)
269 | public func toast(
270 | style: ToastStyle = .custom,
271 | pinToTop: Bool = true,
272 | secondsToPersist: Double? = nil,
273 | animationInDuration: Double = 0.5,
274 | animationOutDuration: Double = 0.5,
275 | padding: Float = 8,
276 | tapHandler: @escaping (UIView) -> Void = { $0.removeFromSuperview() },
277 | _ closure: @escaping () -> UIView
278 | ) {
279 |
280 | // Don't allow more than one Toast to be present
281 | guard toast == nil else {
282 | return
283 | }
284 | didTapToastHandler = tapHandler
285 |
286 | switch style {
287 | case .custom:
288 | toast = closure()
289 | .gesture { UITapGestureRecognizer(target: self, action: #selector(userTappedOnToast)) }
290 | default:
291 | toast = UIView(backgroundColor: .clear) {
292 | closure()
293 | .padding(8)
294 | .configure {
295 | $0.backgroundColor = style.color
296 | $0.clipsToBounds = true
297 | }
298 | .layer(cornerRadius: 8)
299 |
300 | }
301 | .padding(padding)
302 |
303 | .gesture { UITapGestureRecognizer(target: self, action: #selector(userTappedOnToast)) }
304 | }
305 |
306 | toast?.translatesAutoresizingMaskIntoConstraints = false
307 | toast?.alpha = 0
308 |
309 | guard let controller = navigationController,
310 | let containerView = controller.visibleViewController?.view,
311 | let toast = toast else {
312 | destroyToast()
313 | print("Navigate \(#function) Error!")
314 | print("Issue trying to dismiss presentingViewController")
315 | print("Error: Could not unwrap navigationController")
316 | return
317 | }
318 |
319 | controller.visibleViewController?.view.addSubview(toast)
320 | controller.visibleViewController?.view.bringSubviewToFront(toast)
321 |
322 | let pinConstraint = pinToTop ? toast.topAnchor.constraint(equalTo: containerView.safeAreaLayoutGuide.topAnchor) : toast.bottomAnchor.constraint(equalTo: containerView.safeAreaLayoutGuide.bottomAnchor)
323 |
324 | NSLayoutConstraint.activate(
325 | [
326 | pinConstraint,
327 | toast.leadingAnchor.constraint(equalTo: containerView.safeAreaLayoutGuide.leadingAnchor),
328 | toast.trailingAnchor.constraint(equalTo: containerView.safeAreaLayoutGuide.trailingAnchor),
329 | toast.heightAnchor.constraint(greaterThanOrEqualToConstant: 60),
330 | ]
331 | )
332 |
333 | // Animation In
334 | DispatchQueue.main.async {
335 | toast.layoutIfNeeded()
336 | UIView.animate(withDuration: animationInDuration) {
337 | toast.alpha = 1
338 | }
339 | }
340 |
341 | // Animation Out
342 | if let timeToLive = secondsToPersist {
343 | DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + timeToLive) {
344 | UIView.animate(withDuration: animationOutDuration, animations: {
345 | toast.alpha = 0
346 | toast.layoutIfNeeded()
347 | }) { didComplete in
348 | if didComplete {
349 | self.destroyToast()
350 | }
351 | }
352 | }
353 | }
354 | }
355 |
356 | /// Destory the toast
357 | public func destroyToast() {
358 | toast?.removeFromSuperview()
359 | toast = nil
360 | didTapToastHandler = nil
361 | }
362 |
363 | @objc private func userTappedOnToast() {
364 | guard let toast = toast else {
365 | print("Toast \(#function) Error!")
366 | print("Issue trying to dismiss Toast")
367 | print("Error: Could not unwrap Toast")
368 | return
369 | }
370 | didTapToastHandler?(toast)
371 | destroyToast()
372 | }
373 | }
374 |
--------------------------------------------------------------------------------
/Tests/SwiftUIKitTests/Core/BasicSwiftUIKitTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // BasicSwiftUIKitTests.swift
3 | //
4 | //
5 | // Created by Zach Eriksen on 3/2/20.
6 | //
7 |
8 | import Foundation
9 | import XCTest
10 | @testable import SwiftUIKit
11 |
12 | @available(iOS 10.0, *)
13 | final class BasicSwiftUIKitTests: XCTestCase {
14 |
15 | func testDefaultView() {
16 |
17 | let view = UIView()
18 |
19 | XCTAssertNil(view.backgroundColor)
20 | XCTAssertEqual(view.allSubviews.count, 0)
21 | XCTAssertEqual(view.constraints.count, 0)
22 | }
23 |
24 | func testEmbedView() {
25 |
26 | let view = UIView()
27 |
28 | let viewToEmbed = UIView()
29 |
30 | view.embed {
31 | viewToEmbed
32 | }
33 |
34 | let leadingConstraint = view.leadingConstraints.first
35 | let bottomConstraint = view.bottomConstraints.first
36 | let trailingConstraint = view.trailingConstraints.first
37 | let topConstraint = view.topConstraints.first
38 |
39 | XCTAssertNil(view.backgroundColor)
40 | XCTAssertEqual(view.allSubviews.count, 1)
41 | XCTAssertEqual(view.constraints.count, 4)
42 | XCTAssertEqual(leadingConstraint?.constant, 0)
43 | XCTAssertEqual(bottomConstraint?.constant, 0)
44 | XCTAssertEqual(trailingConstraint?.constant, 0)
45 | XCTAssertEqual(topConstraint?.constant, 0)
46 |
47 | view.update(padding: 16)
48 |
49 | XCTAssertEqual(view.constraints.count, 4)
50 | XCTAssertEqual(leadingConstraint?.constant, 16)
51 | XCTAssertEqual(bottomConstraint?.constant, -16)
52 | XCTAssertEqual(trailingConstraint?.constant, -16)
53 | XCTAssertEqual(topConstraint?.constant, 16)
54 | }
55 |
56 | func testEmbedView_WithOnePadding() {
57 |
58 | let view = UIView()
59 |
60 | let viewToEmbed = UIView()
61 |
62 | view.embed(withPadding: [
63 | .leading(16)
64 | ]) {
65 | viewToEmbed
66 | }
67 |
68 | let constraint = view.leadingConstraints.first
69 |
70 | XCTAssertNil(view.backgroundColor)
71 | XCTAssertEqual(view.allSubviews.count, 1)
72 | XCTAssertEqual(view.constraints.count, 1)
73 | XCTAssertEqual(constraint?.constant, 16)
74 |
75 | view.update(padding: .leading(8))
76 | view.update(padding: .trailing(16))
77 |
78 | XCTAssertEqual(constraint?.constant, 8)
79 | XCTAssertEqual(view.constraints.count, 1)
80 | }
81 |
82 | func testEmbedView_WithTwoPadding() {
83 |
84 | let view = UIView()
85 |
86 | let viewToEmbed = UIView()
87 |
88 | view.embed(withPadding: [
89 | .leading(16),
90 | .bottom(16)
91 | ]) {
92 | viewToEmbed
93 | }
94 |
95 | let leadingConstraint = view.leadingConstraints.first
96 | let bottomConstraint = view.bottomConstraints.first
97 |
98 | XCTAssertNil(view.backgroundColor)
99 | XCTAssertEqual(view.allSubviews.count, 1)
100 | XCTAssertEqual(view.constraints.count, 2)
101 | XCTAssertEqual(leadingConstraint?.constant, 16)
102 | XCTAssertEqual(bottomConstraint?.constant, -16)
103 |
104 | view.update(padding: .leading(8))
105 | view.update(padding: .bottom(32))
106 |
107 | XCTAssertEqual(view.constraints.count, 2)
108 | XCTAssertEqual(leadingConstraint?.constant, 8)
109 | XCTAssertEqual(bottomConstraint?.constant, -32)
110 | }
111 |
112 | func testEmbedView_WithThreePadding() {
113 |
114 | let view = UIView()
115 |
116 | let viewToEmbed = UIView()
117 |
118 | view.embed(withPadding: [
119 | .leading(16),
120 | .bottom(16),
121 | .trailing(16)
122 | ]) {
123 | viewToEmbed
124 | }
125 |
126 | let leadingConstraint = view.leadingConstraints.first
127 | let bottomConstraint = view.bottomConstraints.first
128 | let trailingConstraint = view.trailingConstraints.first
129 |
130 | XCTAssertNil(view.backgroundColor)
131 | XCTAssertEqual(view.allSubviews.count, 1)
132 | XCTAssertEqual(view.constraints.count, 3)
133 | XCTAssertEqual(leadingConstraint?.constant, 16)
134 | XCTAssertEqual(bottomConstraint?.constant, -16)
135 | XCTAssertEqual(trailingConstraint?.constant, -16)
136 |
137 | view.update(padding: [.leading(32), .trailing(32), .bottom(32)])
138 |
139 | XCTAssertEqual(view.constraints.count, 3)
140 | XCTAssertEqual(leadingConstraint?.constant, 32)
141 | XCTAssertEqual(bottomConstraint?.constant, -32)
142 | XCTAssertEqual(trailingConstraint?.constant, -32)
143 | }
144 |
145 | func testEmbedView_WithAllPadding() {
146 |
147 | let view = UIView()
148 |
149 | let viewToEmbed = UIView()
150 |
151 | view.embed(withPadding: [
152 | .leading(16),
153 | .bottom(16),
154 | .trailing(16),
155 | .top(16)
156 | ]) {
157 | viewToEmbed
158 | }
159 |
160 | let leadingConstraint = view.leadingConstraints.first
161 | let bottomConstraint = view.bottomConstraints.first
162 | let trailingConstraint = view.trailingConstraints.first
163 | let topConstraint = view.topConstraints.first
164 |
165 | XCTAssertNil(view.backgroundColor)
166 | XCTAssertEqual(view.allSubviews.count, 1)
167 | XCTAssertEqual(view.constraints.count, 4)
168 | XCTAssertEqual(leadingConstraint?.constant, 16)
169 | XCTAssertEqual(bottomConstraint?.constant, -16)
170 | XCTAssertEqual(trailingConstraint?.constant, -16)
171 | XCTAssertEqual(topConstraint?.constant, 16)
172 |
173 | view.update(padding: 32)
174 |
175 | XCTAssertEqual(view.constraints.count, 4)
176 | XCTAssertEqual(leadingConstraint?.constant, 32)
177 | XCTAssertEqual(bottomConstraint?.constant, -32)
178 | XCTAssertEqual(trailingConstraint?.constant, -32)
179 | XCTAssertEqual(topConstraint?.constant, 32)
180 | }
181 |
182 | func testEmbedViews() {
183 |
184 | let view = UIView()
185 |
186 | let viewToEmbed = UIView()
187 |
188 | view.embed {
189 | viewToEmbed.embed {
190 | UIView()
191 | }
192 | }
193 |
194 | XCTAssertNil(view.backgroundColor)
195 | XCTAssertEqual(view.allSubviews.count, 2)
196 | XCTAssertEqual(view.constraints.count, 4)
197 | }
198 |
199 | func testVStackView() {
200 |
201 | let viewToEmbed = UIView()
202 |
203 | let stack = VStack {
204 | [
205 | viewToEmbed
206 | ]
207 | }
208 |
209 | XCTAssert(stack.subviews.first.map { type(of: $0) } == UIStackView.self)
210 | XCTAssertEqual(stack.allSubviews.count, 2)
211 | XCTAssertEqual(stack.views.count, 1)
212 | }
213 |
214 | func testVStackViewAppend_one() {
215 |
216 | let viewToEmbed = UIView()
217 |
218 | let stack = VStack {
219 | [
220 | viewToEmbed
221 | ]
222 | }
223 |
224 | stack.update { (views) in
225 | views.append(UIView())
226 | }
227 |
228 | XCTAssert(stack.subviews.first.map { type(of: $0) } == UIStackView.self)
229 | XCTAssertEqual(stack.allSubviews.count, 3)
230 | XCTAssertEqual(stack.views.count, 2)
231 | }
232 |
233 | func testVStackViewAppend_five() {
234 |
235 | let viewToEmbed = UIView()
236 |
237 | let stack = VStack {
238 | [
239 | viewToEmbed
240 | ]
241 | }
242 |
243 | stack.update { (views) in
244 | views += (0 ... 4).map { Label("\($0)") }
245 | }
246 |
247 | XCTAssert(stack.subviews.first.map { type(of: $0) } == UIStackView.self)
248 | XCTAssertEqual(stack.allSubviews.count, 7)
249 | XCTAssertEqual(stack.views.count, 6)
250 | }
251 |
252 | func testHStackView() {
253 |
254 | let viewToEmbed = UIView()
255 |
256 | let stack = HStack {
257 | [
258 | viewToEmbed
259 | ]
260 | }
261 |
262 | XCTAssert(stack.subviews.first.map { type(of: $0) } == UIStackView.self)
263 | XCTAssertEqual(stack.allSubviews.count, 2)
264 | XCTAssertEqual(stack.views.count, 1)
265 | }
266 |
267 | func testHStackViewAppend_one() {
268 |
269 | let viewToEmbed = UIView()
270 |
271 | let stack = VStack {
272 | [
273 | viewToEmbed
274 | ]
275 | }
276 |
277 | stack.update { (views) in
278 | views.append(UIView())
279 | }
280 |
281 | XCTAssert(stack.subviews.first.map { type(of: $0) } == UIStackView.self)
282 | XCTAssertEqual(stack.allSubviews.count, 3)
283 | XCTAssertEqual(stack.views.count, 2)
284 | }
285 |
286 | func testHStackViewAppend_five() {
287 | let viewToEmbed = UIView()
288 |
289 | let stack = VStack {
290 | [
291 | viewToEmbed
292 | ]
293 | }
294 |
295 | stack.update { (views) in
296 | views += (0 ... 4).map { Label("\($0)") }
297 | }
298 |
299 | XCTAssert(stack.subviews.first.map { type(of: $0) } == UIStackView.self)
300 | XCTAssertEqual(stack.allSubviews.count, 7)
301 | XCTAssertEqual(stack.views.count, 6)
302 | }
303 |
304 | func testZStackView() {
305 |
306 | let viewToEmbed = UIView()
307 |
308 | let stack = ZStack {
309 | [
310 | viewToEmbed
311 | ]
312 | }
313 |
314 | XCTAssertEqual(stack.allSubviews.count, 1)
315 | }
316 |
317 | func testPaddingView() {
318 |
319 | let view = UIView().padding()
320 |
321 | XCTAssertEqual(view.allSubviews.count, 1)
322 | }
323 |
324 | func testConfigureView() {
325 |
326 | let view = UIView(backgroundColor: .red)
327 | .configure {
328 | $0.backgroundColor = .blue
329 | $0.isHidden = true
330 | $0.tintColor = .green
331 | $0.clipsToBounds = true
332 | }
333 |
334 | let otherView = UIView(backgroundColor: .blue)
335 | .hide(if: true)
336 | .clipsToBounds(true)
337 |
338 | XCTAssertEqual(view.backgroundColor, .blue)
339 | XCTAssertEqual(view.isHidden, true)
340 | XCTAssertEqual(view.tintColor, .green)
341 | XCTAssertEqual(view.clipsToBounds, true)
342 |
343 | XCTAssertEqual(view.backgroundColor, otherView.backgroundColor)
344 | XCTAssertEqual(view.isHidden, otherView.isHidden)
345 | XCTAssertEqual(view.clipsToBounds, otherView.clipsToBounds)
346 | }
347 |
348 | func testLayerView() {
349 |
350 | let view = UIView()
351 | .layer {
352 | $0.borderColor = UIColor.blue.cgColor
353 | $0.borderWidth = 3
354 | $0.cornerRadius = 8
355 | $0.masksToBounds = true
356 | }
357 |
358 | let otherView = UIView()
359 | .layer(cornerRadius: 8)
360 |
361 | XCTAssertEqual(view.layer.borderColor, UIColor.blue.cgColor)
362 | XCTAssertEqual(view.layer.borderWidth, 3)
363 | XCTAssertEqual(view.layer.cornerRadius, 8)
364 | XCTAssertEqual(view.layer.masksToBounds, true)
365 |
366 | XCTAssertEqual(view.layer.cornerRadius, otherView.layer.cornerRadius)
367 | }
368 |
369 | func testClearView() {
370 |
371 | let switchView = Switch()
372 | let uiSwitchView = UISwitch()
373 |
374 | let view = UIView().embed {
375 | UIView().vstack {
376 | [
377 | Image(color: .blue),
378 | Switch()
379 | ]
380 | }
381 | }
382 |
383 | let otherView = UIView().embed {
384 | VStack {
385 | [
386 | Image(color: .blue),
387 | Switch()
388 | ]
389 | }
390 | }
391 |
392 | let viewWithoutSwitch = UIView().embed {
393 | UIView().vstack {
394 | [
395 | Image(color: .blue)
396 | ]
397 | }
398 | }
399 |
400 | /** Will fail unless...
401 | === (iOS >= 13) ===
402 | */
403 | XCTAssertEqual(switchView.allSubviews.count, 8)
404 | XCTAssertEqual(uiSwitchView.allSubviews.count, 8)
405 | XCTAssertEqual(view.allSubviews.count, 12)
406 | XCTAssertEqual(otherView.allSubviews.count, 12)
407 | /**
408 | === (End) ===
409 | */
410 |
411 | XCTAssertEqual(viewWithoutSwitch.allSubviews.count, 3)
412 |
413 | switchView.clear()
414 | uiSwitchView.clear()
415 |
416 | view.clear()
417 | otherView.clear()
418 | viewWithoutSwitch.clear()
419 |
420 | XCTAssertEqual(switchView.allSubviews.count, 0)
421 | XCTAssertEqual(uiSwitchView.allSubviews.count, 0)
422 |
423 | XCTAssertEqual(view.allSubviews.count, 0)
424 | XCTAssertEqual(otherView.allSubviews.count, 0)
425 | XCTAssertEqual(viewWithoutSwitch.allSubviews.count, 0)
426 | }
427 |
428 | func testLayoutConstraint() {
429 | let innerView = UIView(backgroundColor: .blue)
430 | .frame(height: 100, width: 100)
431 | let stack = ZStack {
432 | [
433 | innerView
434 | ]
435 | }
436 |
437 |
438 | stack
439 | .activateLayoutConstraints {
440 | [
441 | innerView.centerXAnchor.constraint(equalTo: stack.centerXAnchor),
442 | innerView.centerYAnchor.constraint(equalTo: stack.centerYAnchor)
443 | ]
444 | }
445 |
446 | XCTAssertEqual(innerView.constraints.count, 2)
447 | XCTAssertEqual(stack.constraints.count, 2)
448 | }
449 | }
450 |
--------------------------------------------------------------------------------