├── _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 | ![Example SwiftUIKit](assets/exampleView_01.png) 108 | 109 | ## GitHub Supporters 110 | 111 | [suzyfendrick](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 | ![](https://github.com/oneleif/olDocs/blob/master/assets/images/oneleif_logos/full_logo/oneleif_whiteback.png) 120 | 121 | 122 | 123 | [![](https://img.shields.io/badge/oneleif-Twitter-blue.svg)](https://twitter.com/oneleifdev) 124 | 125 | [![](https://img.shields.io/badge/oneleif-YouTube-red.svg)](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://img.shields.io/badge/oneleif-Discord-7284be.svg)](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 | --------------------------------------------------------------------------------