diff --git a/.github/workflows/swift.yml b/.github/workflows/swift.yml new file mode 100644 index 0000000..7784c59 --- /dev/null +++ b/.github/workflows/swift.yml @@ -0,0 +1,28 @@ +name: Swift + +on: [push] + +jobs: + build: + + runs-on: macOS-latest + strategy: + matrix: + destination: ['platform=iOS Simulator,OS=13.1,name=iPhone 8'] + xcode: ['/Applications/Xcode_11.1.app/Contents/Developer'] + steps: + - uses: actions/checkout@v1 + # Github Actions' machines do in fact have recent versions of Xcode, + # but you may have to explicitly switch to them. We explicitly want + # to use Xcode 11, so we use xcode-select to switch to it. + - name: Switch to Xcode 11 + run: sudo xcode-select --switch /Applications/Xcode_11.1.app + # Since we want to be running our tests from Xcode, we need to + # generate an .xcodeproj file. Luckly, Swift Package Manager has + # build in functionality to do so. + - name: Generate xcodeproj + run: swift package generate-xcodeproj + # Finally, we invoke xcodebuild to run the tests on an iPhone 11 + # simulator. + - name: Run tests + run: xcodebuild test -destination 'name=iPhone 11' -scheme 'SwiftUIKit-Package' diff --git a/README.md b/README.md index 61ef44d..b57c4c0 100644 --- a/README.md +++ b/README.md @@ -58,7 +58,7 @@ class ViewController: UIViewController { }, NavButton(destination: UIViewController { - View(backgroundColor: .white) { + UIView(backgroundColor: .white) { LoadingImage(URL(string: "https://cdn11.bigcommerce.com/s-oe2q4reh/images/stencil/2048x2048/products/832/1401/Beige_Pekingese_Puppy__21677.1568609759.jpg")!) .contentMode(.scaleAspectFit) } @@ -100,7 +100,7 @@ class ViewController: UIViewController { # oneleif Project -![](https://github.com/oneleif/olWebsite/blob/master/Public/images/oneleif.png?raw=true) +![](https://github.com/oneleif/olDocs/blob/master/assets/images/oneleif_logos/full_logo/oneleif_whiteback.png) ### Project Info diff --git a/Sources/SwiftUIKit/Extensions/CALayer+SwiftUIKit.swift b/Sources/SwiftUIKit/Extensions/CALayer+SwiftUIKit.swift new file mode 100644 index 0000000..8a03fc7 --- /dev/null +++ b/Sources/SwiftUIKit/Extensions/CALayer+SwiftUIKit.swift @@ -0,0 +1,265 @@ +// +// CALayer+SwiftUIKit.swift +// SwiftUIKit +// +// Created by Oskar on 08/03/2020. +// + +import UIKit + +@available(iOS 9.0, *) +public extension UIView { + + /// Change layer's background color + /// - Parameter color: You can use `UIColor.colorName.cgColor` to pass UIColor value + @discardableResult + func layer(backgroundColor color: CGColor?) -> Self { + layer.backgroundColor = color + + return self + } + + /// Change layer's background color + @discardableResult + func layer(backgroundColor color: UIColor?) -> Self { + layer.backgroundColor = color?.cgColor + + return self + } + + /// Change layer's content's gravity. + /// - Parameter gravity: Will be used as layer's gravity. + @discardableResult + func layer(contentsGravity: CALayerContentsGravity) -> Self { + layer.contentsGravity = contentsGravity + + return self + } + + /// Change layer's corner radius. + /// - Parameter radius: value, defines corner radius. + @discardableResult + func layer(cornerRadius: Float) -> Self { + layer.cornerRadius = CGFloat(cornerRadius) + + return self + } + + /// Change layer's border color. + /// - Parameter color: use `UIColor.colorName.cgColor` to pass UIColor value. + @discardableResult + func layer(borderColor: CGColor?) -> Self { + layer.borderColor = borderColor + + return self + } + + /// Change layer's border color. + @discardableResult + func layer(borderColor: UIColor?) -> Self { + layer.borderColor = borderColor?.cgColor + + return self + } + + /// Change layer's border width. + /// - Parameter width: Will be used as width value. + @discardableResult + func layer(borderWidth: Float) -> Self { + layer.borderWidth = CGFloat(borderWidth) + return self + } + + /// Change layer's opacity + /// - Parameter value: Will be used as opacity value. + @discardableResult + func layer(opacity: Float) -> Self { + layer.opacity = opacity + + return self + } + + /// Change layer's isHidden value + @discardableResult + func layer(isHidden: Bool) -> Self { + layer.isHidden = isHidden + + return self + } + + /// Set masks to bounds value. + @discardableResult + func layer(masksToBounds: Bool) -> Self { + layer.masksToBounds = masksToBounds + + return self + } + + /// Set layer's mask. + /// - Parameter layer: Allows to set existing type or create new one inside closure. + @discardableResult + func layer(mask: @autoclosure () -> CALayer?) -> Self { + self.layer.mask = mask() + + return self + } + + /// Set `isDoubleSided` parameter. + @discardableResult + func layer(isDoubleSided: Bool) -> Self { + layer.isDoubleSided = isDoubleSided + + return self + } + + /// Set masked corners value. + /// - Parameter corners: You can pass exisiting value or create it inside closure + @available(iOS 11.0, *) + @discardableResult + func layer(maskedCorners: @autoclosure () -> CACornerMask) -> Self { + layer.maskedCorners = maskedCorners() + + return self + } + + /// Set layer's shadow opacity. + /// - Parameter opacity: + @discardableResult + func layer(shadowOpacity: Float) -> Self { + layer.shadowOpacity = shadowOpacity + + return self + } + + /// Change layer's shadow color. + /// - Parameter color: Pass `UIColor.colorName.cgColor` to pass UIColor value + @discardableResult + func layer(shadowColor: CGColor?) -> Self { + layer.shadowColor = shadowColor + + return self + } + + /// Change layer's shadow color. + @discardableResult + func layer(shadowColor: UIColor?) -> Self { + layer.shadowColor = shadowColor?.cgColor + + return self + } + + /// Set layer's shadow radius. Takes `Float` and converts it to `CGFloat` for programmer's convenience + /// - Parameter radius: + @discardableResult + func layer(shadowRadius: Float) -> Self { + layer.shadowRadius = CGFloat(shadowRadius) + + return self + } + + /// Changes layer's shadowOffset to given in parameter + /// - Parameter offset: offset value, can be calculated using closure inside or just add ready one. + @discardableResult + func layer(shadowOffset: @autoclosure () -> CGSize) -> Self { + layer.shadowOffset = shadowOffset() + + return self + } + + /// Set shadow path by passing existing object or use curly braces to create own one. + /// - Parameter path: + @discardableResult + func layer(shadowPath: @autoclosure () -> CGPath?) -> Self { + layer.shadowPath = shadowPath() + + return self + } + + /// Set layer's `allowsEdgeAntialiasing`. + @discardableResult + func layer(allowsEdgeAntialiasing: Bool) -> Self { + layer.allowsEdgeAntialiasing = allowsEdgeAntialiasing + + return self + } + + /// Set layer's `allowsGroupOpacity`. + @discardableResult + func layer(allowsGroupOpacity: Bool) -> Self { + layer.allowsGroupOpacity = allowsGroupOpacity + + return self + } + + /// Set layer's style. + /// - Parameter style: You can pass exisiting value or create new one inside closure. + @discardableResult + func layer(style: @autoclosure () -> [AnyHashable: Any]?) -> Self { + layer.style = style() + + return self + } + + /// Set layer's filters. + /// - Parameter filters: You can pass exisiting value or create new one inside closure. + @discardableResult + func layer(filters: @autoclosure () -> [Any]?) -> Self { + layer.filters = filters() + + return self + } + + /// Set layer's `isOpaque` Boolean. + @discardableResult + func layer(isOpaque: Bool) -> Self { + layer.isOpaque = isOpaque + + return self + } + + /// Set layer's `drawsAsynchronously` + @discardableResult + func layer(drawsAsynchronously: Bool) -> Self { + layer.drawsAsynchronously = drawsAsynchronously + + return self + } + + /// Add animation to a layer. + /// - Parameters: + /// - key: Key which allows to identify given animation. + /// - animation: You can pass existing value or create it inside closure. + @discardableResult + func layer(addAnimation animation: CAAnimation, forKey key: String?) -> Self { + layer.add(animation, forKey: key) + + return self + } + + /// Remove layer's animation specified by given key. + /// - Parameter key: Key that's assigned to animation. + @discardableResult + func layer(removeAnimationForKey key: String) -> Self { + layer.removeAnimation(forKey: key) + + return self + } + + /// Remove all existing layer's animations. + @discardableResult + func removeAllAnimationsFromLayer() -> Self { + layer.removeAllAnimations() + + return self + } + + /// Modify the object's layer + /// - Parameters: + /// - closure: A trailing closure that receives itself.layer inside the closue + @discardableResult + func layer(_ closure: (CALayer) -> Void) -> Self { + closure(layer) + + return self + } +} diff --git a/Sources/SwiftUIKit/Extensions/NSMutableAttributedString+SwiftUIKit.swift b/Sources/SwiftUIKit/Extensions/NSMutableAttributedString+SwiftUIKit.swift index 725c5e3..90ec15b 100644 --- a/Sources/SwiftUIKit/Extensions/NSMutableAttributedString+SwiftUIKit.swift +++ b/Sources/SwiftUIKit/Extensions/NSMutableAttributedString+SwiftUIKit.swift @@ -33,7 +33,6 @@ public extension StringAttributes { /// - value: The value for the attribute being modifed @discardableResult mutating func add(key: AttributedStringKey, value: Any) -> StringAttributes { - self[key] = value return self @@ -48,7 +47,6 @@ public extension NSMutableAttributedString { /// - range: Closed Int Range. Example: 0 ... 3 @discardableResult func set(attributes: StringAttributes, range: ClosedRange) -> Self { - self.setAttributes(attributes, range: NSRange(location: range.lowerBound, length: range.upperBound)) return self diff --git a/Sources/SwiftUIKit/Extensions/UIAlertAction+SwiftUIKit.swift b/Sources/SwiftUIKit/Extensions/UIAlertAction+SwiftUIKit.swift index ebb546d..d4cd49f 100644 --- a/Sources/SwiftUIKit/Extensions/UIAlertAction+SwiftUIKit.swift +++ b/Sources/SwiftUIKit/Extensions/UIAlertAction+SwiftUIKit.swift @@ -13,7 +13,6 @@ public extension UIAlertAction { return UIAlertAction(title: "Cancel", style: .cancel, handler: nil) } - /// Quick Dismiss UIAlertAction class var dismiss: UIAlertAction { return UIAlertAction(title: "Dismiss", style: .cancel, handler: nil) diff --git a/Sources/SwiftUIKit/Extensions/UIView+SwiftUIKit.swift b/Sources/SwiftUIKit/Extensions/UIView+SwiftUIKit.swift index f599507..72fa5c4 100644 --- a/Sources/SwiftUIKit/Extensions/UIView+SwiftUIKit.swift +++ b/Sources/SwiftUIKit/Extensions/UIView+SwiftUIKit.swift @@ -7,9 +7,36 @@ import UIKit +public enum Padding { + case leading(Float) + case trailing(Float) + case top(Float) + case bottom(Float) +} + @available(iOS 9.0, *) public extension UIView { + convenience init(withPadding padding: Float = 0, + backgroundColor: UIColor? = .clear, + _ closure: (() -> UIView)? = nil) { + self.init(frame: .zero) + + self.backgroundColor = backgroundColor + + _ = closure.map { embed(withPadding: padding, $0) } + } + + convenience init(withPadding padding: [Padding], + backgroundColor: UIColor? = .clear, + _ closure: (() -> UIView)? = nil) { + self.init(frame: .zero) + + self.backgroundColor = backgroundColor + + _ = closure.map { embed(withPadding: padding, $0) } + } + /// Embed a Stack /// - Parameters: /// - withSpacing: The amount of spacing between each child view @@ -41,6 +68,37 @@ public extension UIView { return self } + /// Embed a Stack + /// - Parameters: + /// - withSpacing: The amount of spacing between each child view + /// - padding: The amount of space between this view and its parent view + /// - alignment: The layout of arranged views perpendicular to the stack view’s axis (source: UIStackView.Alignment) + /// - distribution: The layout that defines the size and position of the arranged views along the stack view’s axis (source: UIStackView.Distribution) + /// - axis: Keys that specify a horizontal or vertical layout constraint between objects (source: NSLayoutConstraint.Axis) + /// - closure: A trailing closure that accepts an array of views + @discardableResult + func stack(withSpacing spacing: Float = 0, + padding: [Padding], + alignment: UIStackView.Alignment = .fill, + distribution: UIStackView.Distribution = .fill, + axis: NSLayoutConstraint.Axis, + _ closure: () -> [UIView?]) -> Self { + let viewsInVStack = closure() + .compactMap { $0 } + + let stackView = UIStackView(arrangedSubviews: viewsInVStack) + stackView.spacing = CGFloat(spacing) + stackView.alignment = alignment + stackView.distribution = distribution + stackView.axis = axis + + embed(withPadding: padding) { + stackView + } + + return self + } + /// Embed a VStack /// - Parameters: /// - withSpacing: The amount of spacing between each child view @@ -62,6 +120,27 @@ public extension UIView { closure) } + /// Embed a VStack + /// - Parameters: + /// - withSpacing: The amount of spacing between each child view + /// - padding: The amount of space between this view and its parent view + /// - alignment: The layout of arranged views perpendicular to the stack view’s axis (source: UIStackView.Alignment) + /// - distribution: The layout that defines the size and position of the arranged views along the stack view’s axis (source: UIStackView.Distribution) + /// - closure: A trailing closure that accepts an array of views + @discardableResult + func vstack(withSpacing spacing: Float = 0, + padding: [Padding], + alignment: UIStackView.Alignment = .fill, + distribution: UIStackView.Distribution = .fill, + _ closure: () -> [UIView?]) -> Self { + return stack(withSpacing: spacing, + padding: padding, + alignment: alignment, + distribution: distribution, + axis: .vertical, + closure) + } + /// Embed a HStack /// - Parameters: /// - withSpacing: The amount of spacing between each child view @@ -83,6 +162,27 @@ public extension UIView { closure) } + /// Embed a HStack + /// - Parameters: + /// - withSpacing: The amount of spacing between each child view + /// - padding: The amount of space between this view and its parent view + /// - alignment: The layout of arranged views perpendicular to the stack view’s axis (source: UIStackView.Alignment) + /// - distribution: The layout that defines the size and position of the arranged views along the stack view’s axis (source: UIStackView.Distribution) + /// - closure: A trailing closure that accepts an array of views + @discardableResult + func hstack(withSpacing spacing: Float = 0, + padding: [Padding], + alignment: UIStackView.Alignment = .fill, + distribution: UIStackView.Distribution = .fill, + _ closure: () -> [UIView?]) -> Self { + return stack(withSpacing: spacing, + padding: padding, + alignment: alignment, + distribution: distribution, + axis: .horizontal, + closure) + } + /// Embed a View to all anchors (top, bottom, leading, trailing) /// - Parameters: /// - withPadding: The amount of space between the embedded view and this view @@ -104,6 +204,35 @@ public extension UIView { return self } + /// Embed a View to certain anchors (top, bottom, leading, trailing) + /// - Parameters: + /// - withPadding: The amount of space between the embedded view and this view + /// - closure: A trailing closure that accepts a view + @discardableResult + func embed(withPadding padding: [Padding], + _ closure: () -> UIView) -> Self { + let viewToEmbed = closure() + viewToEmbed.translatesAutoresizingMaskIntoConstraints = false + addSubview(viewToEmbed) + + NSLayoutConstraint.activate( + padding.map { + switch $0 { + case .leading(let constant): + return viewToEmbed.leadingAnchor.constraint(equalTo: leadingAnchor, constant: CGFloat(constant)) + case .trailing(let constant): + return viewToEmbed.trailingAnchor.constraint(equalTo: trailingAnchor, constant: CGFloat(-constant)) + case .top(let constant): + return viewToEmbed.topAnchor.constraint(equalTo: topAnchor, constant: CGFloat(constant)) + case .bottom(let constant): + return viewToEmbed.bottomAnchor.constraint(equalTo: bottomAnchor, constant: CGFloat(-constant)) + } + } + ) + + return self + } + /// Clear all subviews from this view @discardableResult func clear() -> Self { @@ -116,8 +245,8 @@ public extension UIView { /// - Parameters: /// - padding: The amount of space between this view and its parent view @discardableResult - func padding(_ padding: Float = 8) -> View { - return View(backgroundColor: backgroundColor) + func padding(_ padding: Float = 8) -> UIView { + return UIView(backgroundColor: backgroundColor) .embed(withPadding: padding) { self } .accessibility(label: accessibilityLabel, identifier: accessibilityIdentifier, @@ -130,7 +259,6 @@ public extension UIView { /// - width: Value for the widthAnchor @discardableResult func frame(height: Float? = nil, width: Float? = nil) -> Self { - if let height = height { heightAnchor.constraint(equalToConstant: CGFloat(height)).isActive = true } @@ -142,13 +270,66 @@ public extension UIView { return self } + /// Update the height and width anchors to constant values (if nil it will not update the constraint) + /// - Parameters: + /// - height: Value for the heightAnchor + /// - width: Value for the widthAnchor + @available(iOS 10.0, *) + @discardableResult + func update(height: Float? = nil, width: Float? = nil) -> Self { + if let height = height { + constraints.first { (constraint) -> Bool in + constraint.firstAnchor == heightAnchor + }?.constant = CGFloat(height) + } + + if let width = width { + constraints.first { (constraint) -> Bool in + constraint.firstAnchor == widthAnchor + }?.constant = CGFloat(width) + } + + return self + } + + /// Remove the height anchor constraint + @available(iOS 10.0, *) + @discardableResult + func removeHeightConstraint() -> Self { + if let heightConstraint = constraints.first(where: { $0.firstAnchor == heightAnchor }) { + removeConstraint(heightConstraint) + } + + return self + } + + /// Remove the width anchor constraint + @available(iOS 10.0, *) + @discardableResult + func removeWidthConstraint() -> Self { + if let widthConstraint = constraints.first(where: { $0.firstAnchor == widthAnchor }) { + removeConstraint(widthConstraint) + } + + return self + } + + /// Activate LayoutConstraints + /// - Parameters: + /// - constraints: A trailing closure that accepts an array of NSLayoutConstraint + @discardableResult + func activateLayoutConstraints(_ constraints: () -> [NSLayoutConstraint]) -> Self { + NSLayoutConstraint.activate(constraints()) + + return self + } + /// Offset the View's center by (x, y) /// - Parameters: /// - x: Value to add to the center.x /// - y: Value to add to the center.y @discardableResult func offset(x: Float? = nil, y: Float? = nil) -> Self { - if let x = x { center.x += CGFloat(x) } @@ -165,7 +346,6 @@ public extension UIView { /// - y: Value to set the center.y @discardableResult func center(x: Float? = nil, y: Float? = nil) -> Self { - if let x = x { center.x = CGFloat(x) } @@ -180,37 +360,12 @@ public extension UIView { /// - Parameters: /// - if: A closure that determines if the view should be hidden @discardableResult - func hide(if shouldHide: () -> Bool) -> Self { + func hide(if shouldHide: @autoclosure () -> Bool) -> Self { isHidden = shouldHide() return self } - /// Hide the view - /// - Parameters: - /// - if: A Bool that determines if the view should be hidden - @discardableResult - func hide(if shouldHide: Bool) -> Self { - isHidden = shouldHide - - return self - } - - /// Modify the object's layer - /// - Parameters: - /// - closure: A trailing closure that receives itself.layer inside the closue - @discardableResult - func layer(_ closure: (CALayer) -> Void) -> Self { - closure(layer) - - return self - } - - /// Modify the object's accessibility - /// - Parameters: - /// - label: A succinct label that identifies the accessibility element, in a localized string (source: accessibilityLabel) - /// - identifier: A string that identifies the element (source: accessibilityIdentifier) - /// - traits: The combination of accessibility traits that best characterize the accessibility element (source: accessibilityTraits) @discardableResult func accessibility(label: String? = nil, identifier: String? = nil, @@ -236,7 +391,7 @@ public extension UIView { } @discardableResult - func background(color: UIColor) -> Self { + func background(color: UIColor?) -> Self { backgroundColor = color return self @@ -248,13 +403,6 @@ public extension UIView { return self } - - @discardableResult - func corner(radius: Float) -> Self { - layer.cornerRadius = CGFloat(radius) - - return self - } } @available(iOS 9.0, *) diff --git a/Sources/SwiftUIKit/Navigation/Navigate.swift b/Sources/SwiftUIKit/Navigation/Navigate.swift index 5390b65..f1db03b 100644 --- a/Sources/SwiftUIKit/Navigation/Navigate.swift +++ b/Sources/SwiftUIKit/Navigation/Navigate.swift @@ -273,21 +273,21 @@ public class Navigate { switch style { case .custom: toast = closure() - .gesture{ UITapGestureRecognizer(target: self, action: #selector(userTappedOnToast)) } + .gesture { UITapGestureRecognizer(target: self, action: #selector(userTappedOnToast)) } default: - toast = View(backgroundColor: .clear) { + toast = UIView(backgroundColor: .clear) { closure() .padding(8) .configure { $0.backgroundColor = style.color $0.clipsToBounds = true } - .layer { $0.cornerRadius = 8 } + .layer(cornerRadius: 8) } .padding(padding) - .gesture{ UITapGestureRecognizer(target: self, action: #selector(userTappedOnToast)) } + .gesture { UITapGestureRecognizer(target: self, action: #selector(userTappedOnToast)) } } toast?.translatesAutoresizingMaskIntoConstraints = false @@ -296,6 +296,7 @@ public class Navigate { guard let controller = navigationController, let containerView = controller.visibleViewController?.view, let toast = toast else { + destroyToast() print("Navigate \(#function) Error!") print("Issue trying to dismiss presentingViewController") print("Error: Could not unwrap navigationController") diff --git a/Sources/SwiftUIKit/Views/LoadingView.swift b/Sources/SwiftUIKit/Views/LoadingView.swift index f63f6c0..7c6053b 100644 --- a/Sources/SwiftUIKit/Views/LoadingView.swift +++ b/Sources/SwiftUIKit/Views/LoadingView.swift @@ -31,7 +31,6 @@ public class LoadingView: UIActivityIndicatorView { public extension LoadingView { @discardableResult func start() -> Self { - startAnimating() return self @@ -39,7 +38,6 @@ public extension LoadingView { @discardableResult func stop() -> Self { - stopAnimating() return self diff --git a/Sources/SwiftUIKit/Views/TableView.swift b/Sources/SwiftUIKit/Views/TableView.swift new file mode 100644 index 0000000..4ddea84 --- /dev/null +++ b/Sources/SwiftUIKit/Views/TableView.swift @@ -0,0 +1,200 @@ +// +// TableView.swift +// SwiftUIKit +// +// Created by Zach Eriksen on 4/12/20. +// + +import UIKit + +@available(iOS 9.0, *) +public protocol CellDisplayable { + var cellID: String { get } +} + +@available(iOS 9.0, *) +public protocol DataConfigurable: UITableViewCell { + static var ID: String { get } +} + +@available(iOS 9.0, *) +public protocol CellUpdatable: UITableViewCell { + func update(forData data: CellDisplayable) +} + +@available(iOS 9.0, *) +public protocol CellConfigurable: UITableViewCell { + func configure(forData data: CellDisplayable) +} + +@available(iOS 9.0, *) +public typealias TableViewCell = DataConfigurable & CellConfigurable & CellUpdatable + +public typealias TableHeaderFooterViewHandler = (Int) -> UIView? +public typealias TableHeaderFooterTitleHandler = (Int) -> String? + +@available(iOS 9.0, *) +public class TableView: UITableView { + public var data: [[CellDisplayable]] + + fileprivate var headerViewForSection: TableHeaderFooterViewHandler? + fileprivate var footerViewForSection: TableHeaderFooterViewHandler? + fileprivate var headerTitleForSection: TableHeaderFooterTitleHandler? + fileprivate var footerTitleForSection: TableHeaderFooterTitleHandler? + + public init(initalData: [[CellDisplayable]] = [[CellDisplayable]](), + style: UITableView.Style = .plain) { + self.data = initalData + super.init(frame: .zero, style: style) + + dataSource = self + delegate = self + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +@available(iOS 9.0, *) +public extension TableView { + @discardableResult + func update(shouldReloadData: Bool = false, + _ closure: ([[CellDisplayable]]) -> [[CellDisplayable]]) -> Self { + data = closure(data) + + if shouldReloadData { + reloadData() + } + + return self + } + + @discardableResult + func append(shouldReloadData: Bool = false, + _ closure: () -> [[CellDisplayable]]) -> Self { + data += closure() + + if shouldReloadData { + reloadData() + } + + return self + } +} + +@available(iOS 9.0, *) +extension TableView: UITableViewDelegate { + +} + +@available(iOS 9.0, *) +extension TableView: UITableViewDataSource { + func sections() -> Int { + data.count + } + + func rows(forSection section: Int) -> Int { + data[section].count + } + + public func numberOfSections(in tableView: UITableView) -> Int { + sections() + } + + public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + rows(forSection: section) + } + + public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cellData = data[indexPath.section][indexPath.row] + + let cell = tableView.dequeueReusableCell(withIdentifier: cellData.cellID, for: indexPath) + + if let configure = cell as? CellUpdatable { + configure.update(forData: cellData) + } + + guard cell.contentView.allSubviews.count == 0 else { + return cell + } + + if let configure = cell as? CellConfigurable { + configure.configure(forData: cellData) + } + + return cell + } + + public func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { + headerViewForSection?(section) + } + + public func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { + headerTitleForSection?(section) + } + + public func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? { + footerViewForSection?(section) + } + + public func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? { + footerTitleForSection?(section) + } +} + +@available(iOS 9.0, *) +public extension TableView { + @discardableResult + func set(dataSource: UITableViewDataSource) -> Self { + self.dataSource = dataSource + + return self + } + + @discardableResult + func set(delegate: UITableViewDelegate) -> Self { + self.delegate = delegate + + return self + } + + @discardableResult + func register(cells: [TableViewCell.Type]) -> Self { + cells.forEach { + register($0, forCellReuseIdentifier: $0.ID) + } + + return self + } + + @discardableResult + func headerView(_ handler: @escaping TableHeaderFooterViewHandler) -> Self { + headerViewForSection = handler + + return self + } + + @discardableResult + func footerView(_ handler: @escaping TableHeaderFooterViewHandler) -> Self { + footerViewForSection = handler + + return self + } + + @discardableResult + func headerTitle(_ handler: @escaping TableHeaderFooterTitleHandler) -> Self { + headerTitleForSection = handler + + return self + } + + @discardableResult + func footerTitle(_ handler: @escaping TableHeaderFooterTitleHandler) -> Self { + footerTitleForSection = handler + + return self + } +} + + diff --git a/Sources/SwiftUIKit/Views/View.swift b/Sources/SwiftUIKit/Views/View.swift deleted file mode 100644 index a07a7f6..0000000 --- a/Sources/SwiftUIKit/Views/View.swift +++ /dev/null @@ -1,25 +0,0 @@ -// -// View.swift -// SwiftUIKit -// -// Created by Zach Eriksen on 10/29/19. -// - -import UIKit - -@available(iOS 9.0, *) -public class View: UIView { - public init(withPadding padding: Float = 0, - backgroundColor: UIColor? = .clear, - _ closure: (() -> UIView)? = nil) { - super.init(frame: .zero) - - self.backgroundColor = backgroundColor - - _ = closure.map { embed(withPadding: padding, $0) } - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } -} diff --git a/Sources/SwiftUIKit/Views/WebView.swift b/Sources/SwiftUIKit/Views/WebView.swift index bc33c9d..601bcc9 100644 --- a/Sources/SwiftUIKit/Views/WebView.swift +++ b/Sources/SwiftUIKit/Views/WebView.swift @@ -28,7 +28,6 @@ public extension WebView { loadHTMLString(baseURL: baseURL, htmlString) } - convenience init(url: URL) { self.init() @@ -55,7 +54,6 @@ public extension WebView { @discardableResult func loadHTMLString(baseURL: URL? = nil, _ htmlString: () -> String) -> Self { - loadHTMLString(htmlString(), baseURL: baseURL) return self @@ -63,7 +61,6 @@ public extension WebView { @discardableResult func load(url: URL) -> Self { - load(URLRequest(url: url)) return self @@ -71,7 +68,6 @@ public extension WebView { @discardableResult func load(request: URLRequest) -> Self { - load(request) return self @@ -79,7 +75,6 @@ public extension WebView { @discardableResult func loadFile(URL: URL, allowingReadAccessTo readAccessURL: URL) -> Self { - loadFileURL(URL, allowingReadAccessTo: readAccessURL) return self @@ -87,7 +82,6 @@ public extension WebView { @discardableResult func load(data: Data, mimeType MIMEType: String, characterEncodingName: String, baseURL: URL) -> Self { - load(data, mimeType: MIMEType, characterEncodingName: characterEncodingName, baseURL: baseURL) return self @@ -95,8 +89,6 @@ public extension WebView { @discardableResult func set(uiDelegate delegate: WKUIDelegate) -> Self { - - uiDelegate = delegate return self @@ -104,8 +96,6 @@ public extension WebView { @discardableResult func set(navigationDelegate delegate: WKNavigationDelegate) -> Self { - - navigationDelegate = delegate return self diff --git a/Tests/SwiftUIKitTests/ADA/BasicADATests.swift b/Tests/SwiftUIKitTests/ADA/BasicADATests.swift new file mode 100644 index 0000000..4eadd75 --- /dev/null +++ b/Tests/SwiftUIKitTests/ADA/BasicADATests.swift @@ -0,0 +1,63 @@ +// +// BasicADATests.swift +// +// +// Created by Zach Eriksen on 3/2/20. +// + +import Foundation +import XCTest +@testable import SwiftUIKit + +@available(iOS 9.0, *) +final class BasicADATests: XCTestCase { + func testLabelADA() { + + let label = Label("SomeString") + .accessibility(identifier: "SomeID") + .padding() + .padding() + .padding() + .debug() + + assert(label.accessibilityLabel == "SomeString") + assert(label.accessibilityIdentifier == "SomeID") + assert(label.accessibilityTraits == .staticText) + } + + func testButtonADA() { + let button = Button("SomeString") { print("Hello") } + .accessibility(label: nil) + + assert(button.accessibilityLabel == "SomeString") + assert(button.accessibilityIdentifier == nil) + assert(button.accessibilityTraits == .button) + } + + func testComplexViewADA() { + let view = UIView { + UIView { + HStack { + [ + Label("Hello World"), + Label("Ipsum") + ] + } + } + } + .accessibility(identifier: "mainView") + + let accessibilityLabels = ["Hello World", "Ipsum"] + + assert(view.allSubviews.compactMap { $0.accessibilityLabel } == accessibilityLabels) + assert(view.accessibilityIdentifier == "mainView") + assert(view.accessibilityTraits == .none) + assert(view.shouldGroupAccessibilityChildren == false) + } + + static var allTests = [ + ("testLabelADA", testLabelADA), + ("testButtonADA", testButtonADA), + ("testComplexViewADA", testComplexViewADA) + ] +} diff --git a/Tests/SwiftUIKitTests/Case/Extensions/CALayer+SwiftUIKitTests.swift b/Tests/SwiftUIKitTests/Case/Extensions/CALayer+SwiftUIKitTests.swift new file mode 100644 index 0000000..79c53fc --- /dev/null +++ b/Tests/SwiftUIKitTests/Case/Extensions/CALayer+SwiftUIKitTests.swift @@ -0,0 +1,219 @@ +// +// UIView+SwiftUIKitTests.swift +// SwiftUIKitTests +// +// Created by Oskar on 06/03/2020. +// + +import XCTest +@testable import SwiftUIKit + +@available(iOS 9.0, *) +class CALayer_SwiftUIKitTests: XCTestCase { + private var label: Label! + + private let testColor: UIColor? = UIColor.red + private let testFloat: Float = 0.5 + private let testCGRect = CGRect(x: 10.0, y: 20.0, width: 200.0, height: 200.0) + private let testKey = "test" + + override func setUp() { + super.setUp() + } + + override func tearDown() { + label = nil + super.tearDown() + } + + func setUpLabel(testing name: String) -> Label { + label = Label("Testing: \(name)") + return label + } + + func testView_layerModifier_borderColor() { + setUpLabel(testing: "borderColor") + .layer(borderColor: testColor) + + XCTAssertEqual(label.layer.borderColor, testColor?.cgColor) + } + + func testView_layerModifier_backgroundColor() { + setUpLabel(testing: "backgroundColor") + .layer(backgroundColor: testColor) + + XCTAssertEqual(label.layer.backgroundColor, testColor?.cgColor) + } + + func testView_layerModifier_borderWidth() { + setUpLabel(testing: "borderWidth") + .layer(borderWidth: testFloat) + + XCTAssertEqual(label.layer.borderWidth, CGFloat(testFloat)) + } + + func testView_layerModifier_opacity() { + setUpLabel(testing: "opacity") + .layer(opacity: testFloat) + + XCTAssertEqual(label.layer.opacity, Float(testFloat)) + } + + func testView_layerModifier_contentsGravity() { + let testGravity: CALayerContentsGravity = .center + + setUpLabel(testing: "contentsGravity") + .layer(contentsGravity: testGravity) + + XCTAssertEqual(label.layer.contentsGravity, testGravity) + } + + func testView_layerModifier_isHidden() { + setUpLabel(testing: "layer's isHidden") + .layer(isHidden: true) + + XCTAssertTrue(label.layer.isHidden) + } + + func testView_layerModifier_masksToBounds() { + setUpLabel(testing: "masksToBounds") + .layer(masksToBounds: true) + + XCTAssertTrue(label.layer.masksToBounds) + } + + func testView_layerModifier_mask() { + let testMask = CALayer() + + setUpLabel(testing: "mask") + .layer(mask: testMask) + + XCTAssertEqual(label.layer.mask, testMask) + } + + func testView_layerModifier_doubleSided() { + setUpLabel(testing: "isDoubleSided") + .layer(isDoubleSided: true) + + XCTAssertTrue(label.layer.isDoubleSided) + } + + @available(iOS 11.0, *) + func testView_layerModifier_maskedCorners() { + let testCornerMask = CACornerMask() + + setUpLabel(testing: "maskedCorners") + .layer(maskedCorners: testCornerMask) + + XCTAssertEqual(label.layer.maskedCorners, testCornerMask) + } + + func testView_layerModifier_shadowOpacity() { + setUpLabel(testing: "shadowOpacity") + .layer(shadowOpacity: testFloat) + + XCTAssertEqual(label.layer.shadowOpacity, testFloat) + } + + func testView_layerModifier_shadowRadius() { + setUpLabel(testing: "shadowRadius") + .layer(shadowRadius: testFloat) + + XCTAssertEqual(label.layer.shadowRadius, CGFloat(testFloat)) + } + + func testView_layerModifier_shadowOffset() { + let testShadowOffset = CGSize(width: 200, height: 200) + + setUpLabel(testing: "shadowOffset") + .layer(shadowOffset: testShadowOffset) + + XCTAssertEqual(label.layer.shadowOffset, testShadowOffset) + } + + func testView_layerModifier_shadowPath() { + let testPath = CGPath(roundedRect: .infinite, cornerWidth: 2, cornerHeight: 5, transform: nil) + + setUpLabel(testing: "shadowPath") + .layer(shadowPath: testPath) + + XCTAssertEqual(label.layer.shadowPath, testPath) + } + + + func testView_layerModifier_allowsEdgeAntialiasing() { + setUpLabel(testing: "allowsEdgeAntialiasing") + .layer(allowsEdgeAntialiasing: true) + + XCTAssertTrue(label.layer.allowsEdgeAntialiasing) + } + + func testView_layerModifier_allowsGroupOpacity() { + setUpLabel(testing: "allowsGroupOpacity") + .layer(allowsGroupOpacity: true) + + XCTAssertTrue(label.layer.allowsGroupOpacity) + } + + func testView_layerModifier_style() { + let testDict = [0: "Test"] + + setUpLabel(testing: "style") + .layer(style: testDict) + + XCTAssertEqual(label.layer.style?.first?.value as? String, + testDict[0]) + } + + func testView_layerModifier_filter() { + let testFilters = [CIFilter(name: "CIGaussianBlur")] + + setUpLabel(testing: "Filters") + .layer(filters: testFilters as [Any]) + + let layers = label.layer.filters as! [CIFilter] + XCTAssertEqual(layers, testFilters) + } + + func testView_layerModifier_addAnimation() { + let testAnimation = CABasicAnimation(keyPath: "addAnimation") + testAnimation.beginTime = CACurrentMediaTime() + 0.2 + testAnimation.duration = 0.5 + testAnimation.fromValue = 0.0 + testAnimation.toValue = 1.0 + + setUpLabel(testing: "opacity") + .layer(addAnimation: testAnimation, forKey: testKey) + + XCTAssertEqual(label.layer.animation(forKey: testKey)?.beginTime, + (testAnimation as CABasicAnimation?)?.beginTime) + } + + func testView_layerModifier_removeAnimation() { + let testAnimation = CABasicAnimation(keyPath: "opacity") + testAnimation.beginTime = CACurrentMediaTime() + 0.2 + testAnimation.duration = 0.5 + testAnimation.fromValue = 0.0 + testAnimation.toValue = 1.0 + + setUpLabel(testing: "removingAnimation") + .layer(addAnimation: testAnimation, forKey: testKey) + .layer(removeAnimationForKey: testKey) + + XCTAssertNil(label.layer.animation(forKey: "test")) + } + + func testView_layerModifier_removeAllAnimations() { + let testAnimation = CABasicAnimation(keyPath: "opacity") + testAnimation.beginTime = CACurrentMediaTime() + 0.2 + testAnimation.duration = 0.5 + testAnimation.fromValue = 0.0 + testAnimation.toValue = 1.0 + + setUpLabel(testing: "removeAllAnimations") + .layer(addAnimation: testAnimation, forKey: testKey) + .removeAllAnimationsFromLayer() + + XCTAssertNil(label.layer.animationKeys()) + } +} diff --git a/Tests/SwiftUIKitTests/Core/BasicSwiftUIKitTests.swift b/Tests/SwiftUIKitTests/Core/BasicSwiftUIKitTests.swift new file mode 100644 index 0000000..e9b97a5 --- /dev/null +++ b/Tests/SwiftUIKitTests/Core/BasicSwiftUIKitTests.swift @@ -0,0 +1,311 @@ +// +// BasicSwiftUIKitTests.swift +// +// +// Created by Zach Eriksen on 3/2/20. +// + +import Foundation +import XCTest +@testable import SwiftUIKit + +@available(iOS 9.0, *) +final class BasicSwiftUIKitTests: XCTestCase { + + func testDefaultView() { + + let view = UIView() + + XCTAssertNil(view.backgroundColor) + XCTAssert(view.allSubviews.count == 0) + XCTAssert(view.constraints.count == 0) + } + + func testEmbedView() { + + let view = UIView() + + let viewToEmbed = UIView() + + view.embed { + viewToEmbed + } + + XCTAssertNil(view.backgroundColor) + XCTAssert(view.allSubviews.count == 1) + XCTAssert(view.constraints.count == 4) + } + + func testEmbedView_WithOnePadding() { + + let view = UIView() + + let viewToEmbed = UIView() + + view.embed(withPadding: [ + .leading(16) + ]) { + viewToEmbed + } + + XCTAssertNil(view.backgroundColor) + XCTAssert(view.allSubviews.count == 1) + XCTAssert(view.constraints.count == 1) + } + + func testEmbedView_WithTwoPadding() { + + let view = UIView() + + let viewToEmbed = UIView() + + view.embed(withPadding: [ + .leading(16), + .bottom(16) + ]) { + viewToEmbed + } + + XCTAssertNil(view.backgroundColor) + XCTAssert(view.allSubviews.count == 1) + XCTAssert(view.constraints.count == 2) + } + + func testEmbedView_WithThreePadding() { + + let view = UIView() + + let viewToEmbed = UIView() + + view.embed(withPadding: [ + .leading(16), + .bottom(16), + .trailing(16) + ]) { + viewToEmbed + } + + XCTAssertNil(view.backgroundColor) + XCTAssert(view.allSubviews.count == 1) + XCTAssert(view.constraints.count == 3) + } + + func testEmbedView_WithAllPadding() { + + let view = UIView() + + let viewToEmbed = UIView() + + view.embed(withPadding: [ + .leading(16), + .bottom(16), + .trailing(16), + .top(16) + ]) { + viewToEmbed + } + + XCTAssertNil(view.backgroundColor) + XCTAssert(view.allSubviews.count == 1) + XCTAssert(view.constraints.count == 4) + } + + func testEmbedViews() { + + let view = UIView() + + let viewToEmbed = UIView() + + view.embed { + viewToEmbed.embed { + UIView() + } + } + + XCTAssertNil(view.backgroundColor) + XCTAssert(view.allSubviews.count == 2) + XCTAssert(view.constraints.count == 4) + } + + func testVStackView() { + + let viewToEmbed = UIView() + + let stack = VStack { + [ + viewToEmbed + ] + } + + XCTAssert(stack.subviews.first.map { type(of: $0) } == UIStackView.self) + XCTAssert(stack.allSubviews.count == 2) + } + + func testHStackView() { + + let viewToEmbed = UIView() + + let stack = HStack { + [ + viewToEmbed + ] + } + + XCTAssert(stack.subviews.first.map { type(of: $0) } == UIStackView.self) + XCTAssert(stack.allSubviews.count == 2) + } + + func testZStackView() { + + let viewToEmbed = UIView() + + let stack = ZStack { + [ + viewToEmbed + ] + } + + XCTAssert(stack.allSubviews.count == 1) + } + + func testPaddingView() { + + let view = UIView().padding() + + XCTAssert(view.allSubviews.count == 1) + } + + func testConfigureView() { + + let view = UIView(backgroundColor: .red) + .configure { + $0.backgroundColor = .blue + $0.isHidden = true + $0.tintColor = .green + $0.clipsToBounds = true + } + + let otherView = UIView(backgroundColor: .blue) + .hide(if: true) + .clipsToBounds(true) + + XCTAssert(view.backgroundColor == .blue) + XCTAssert(view.isHidden == true) + XCTAssert(view.tintColor == .green) + XCTAssert(view.clipsToBounds == true) + + XCTAssert(view.backgroundColor == otherView.backgroundColor) + XCTAssert(view.isHidden == otherView.isHidden) + XCTAssert(view.clipsToBounds == otherView.clipsToBounds) + } + + func testLayerView() { + + let view = UIView() + .layer { + $0.borderColor = UIColor.blue.cgColor + $0.borderWidth = 3 + $0.cornerRadius = 8 + $0.masksToBounds = true + } + + let otherView = UIView() + .layer(cornerRadius: 8) + + XCTAssert(view.layer.borderColor == UIColor.blue.cgColor) + XCTAssert(view.layer.borderWidth == 3) + XCTAssert(view.layer.cornerRadius == 8) + XCTAssert(view.layer.masksToBounds == true) + + XCTAssert(view.layer.cornerRadius == otherView.layer.cornerRadius) + } + + func testClearView() { + + let switchView = Switch() + let uiSwitchView = UISwitch() + + let view = UIView().embed { + UIView().vstack { + [ + Image(.blue), + Switch() + ] + } + } + + let otherView = UIView().embed { + VStack { + [ + Image(.blue), + Switch() + ] + } + } + + let viewWithoutSwitch = UIView().embed { + UIView().vstack { + [ + Image(.blue) + ] + } + } + + XCTAssert(switchView.allSubviews.count == 8, "switchView.allSubviews.count == \(switchView.allSubviews.count)") + XCTAssert(uiSwitchView.allSubviews.count == 8, "uiSwitchView.allSubviews.count == \(uiSwitchView.allSubviews.count)") + + XCTAssert(view.allSubviews.count == 12, "view.allSubviews.count == \(view.allSubviews.count)") + XCTAssert(otherView.allSubviews.count == 12, "otherView.allSubviews.count == \(otherView.allSubviews.count)") + XCTAssert(viewWithoutSwitch.allSubviews.count == 3, "viewWithoutSwitch.allSubviews.count == \(viewWithoutSwitch.allSubviews.count)") + + switchView.clear() + uiSwitchView.clear() + + view.clear() + otherView.clear() + viewWithoutSwitch.clear() + + XCTAssert(switchView.allSubviews.count == 0) + XCTAssert(uiSwitchView.allSubviews.count == 0) + + XCTAssert(view.allSubviews.count == 0) + XCTAssert(otherView.allSubviews.count == 0) + XCTAssert(viewWithoutSwitch.allSubviews.count == 0) + } + + func testLayoutConstraint() { + let innerView = UIView(backgroundColor: .blue) + .frame(height: 100, width: 100) + let stack = ZStack { + [ + innerView + ] + } + + + stack + .activateLayoutConstraints { + [ + innerView.centerXAnchor.constraint(equalTo: stack.centerXAnchor), + innerView.centerYAnchor.constraint(equalTo: stack.centerYAnchor) + ] + } + + XCTAssert(innerView.constraints.count == 2) + XCTAssert(stack.constraints.count == 2) + } + + static var allTests = [ + ("testDefaultView", testDefaultView), + ("testEmbedViews", testEmbedViews), + ("testEmbedView", testEmbedView), + ("testVStackView", testVStackView), + ("testHStackView", testHStackView), + ("testZStackView", testZStackView), + ("testPaddingView", testPaddingView), + ("testConfigureView", testConfigureView), + ("testLayerView", testLayerView), + ("testClearView", testClearView), + ("testLayoutConstraint", testLayoutConstraint) + ] +} diff --git a/Tests/SwiftUIKitTests/SwiftUIKitTests.swift b/Tests/SwiftUIKitTests/NSAttributedString/NSMutableAttributedStringTests.swift similarity index 84% rename from Tests/SwiftUIKitTests/SwiftUIKitTests.swift rename to Tests/SwiftUIKitTests/NSAttributedString/NSMutableAttributedStringTests.swift index 7ed49cd..4dcb440 100644 --- a/Tests/SwiftUIKitTests/SwiftUIKitTests.swift +++ b/Tests/SwiftUIKitTests/NSAttributedString/NSMutableAttributedStringTests.swift @@ -1,30 +1,16 @@ +// +// NSMutableAttributedStringTests.swift +// +// +// Created by Zach Eriksen on 3/2/20. +// + +import Foundation import XCTest @testable import SwiftUIKit @available(iOS 9.0, *) -final class SwiftUIKitTests: XCTestCase { - func testLabelADA() { - - let label = Label("SomeString") - .accessibility(identifier: "SomeID") - .padding() - .padding() - .padding() - .debug() - - assert(label.accessibilityLabel == "SomeString") - assert(label.accessibilityIdentifier == "SomeID") - assert(label.accessibilityTraits == .staticText) - } - - func testButtonADA() { - let button = Button("SomeString") { print("Hello") } - .accessibility(label: nil) - - assert(button.accessibilityLabel == "SomeString") - assert(button.accessibilityIdentifier == nil) - assert(button.accessibilityTraits == .button) - } +final class NSMutableAttributedStringTests: XCTestCase { func testAttributedString() { var usernameAttributes = StringAttributes(for: .font, value: UIFont.preferredFont(forTextStyle: .headline)) @@ -90,8 +76,6 @@ final class SwiftUIKitTests: XCTestCase { } static var allTests = [ - ("testLabelADA", testLabelADA), - ("testButtonADA", testButtonADA), ("testAttributedString", testAttributedString), ("testApplyLabel", testApplyLabel) ] diff --git a/Tests/SwiftUIKitTests/TableView/TableViewTests.swift b/Tests/SwiftUIKitTests/TableView/TableViewTests.swift new file mode 100644 index 0000000..f6fce6d --- /dev/null +++ b/Tests/SwiftUIKitTests/TableView/TableViewTests.swift @@ -0,0 +1,181 @@ +// +// TableViewTests.swift +// SwiftUIKitTests +// +// Created by Zach Eriksen on 4/12/20. +// + +import XCTest +import UIKit +@testable import SwiftUIKit + +@available(iOS 9.0, *) +class TableViewTests: XCTestCase { + + func testTableViewNoCells() { + let table = TableView() + + table.register(cells: []) + + table + .append { + [ + [ + TableTestHelper.InfoData(title: "First Info!", count: 01, bio: "This is the very first!"), + 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."), + TableTestHelper.InfoData(title: "2nd Section!", count: 2222, bio: "2") + ] + + ] + } + + XCTAssertEqual(table.sections(), 1) + XCTAssertEqual(table.rows(forSection: 0), 3) + } + + func testTableViewAppend() { + let table = TableView() + + table.register(cells: [ + TableTestHelper.InfoCell.self, + ]) + + table + .append { + [ + [ + TableTestHelper.InfoData(title: "First Info!", count: 01, bio: "This is the very first!"), + 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."), + TableTestHelper.InfoData(title: "2nd Section!", count: 2222, bio: "2") + ] + + ] + } + + XCTAssertEqual(table.sections(), 1) + XCTAssertEqual(table.rows(forSection: 0), 3) + } + + func testTableViewUpdate() { + let table = TableView() + + table.register(cells: [ + TableTestHelper.InfoCell.self, + ]) + + table.append { + [ + [TableTestHelper.InfoData(title: "First Info!", count: 01, bio: "This is the very first!")] + ] + } + + table + .update { data in + [ + [ + 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."), + TableTestHelper.InfoData(title: "2nd Section!", count: 2222, bio: "2") + ] + + ] + + data + } + + XCTAssertEqual(table.sections(), 2) + XCTAssertEqual(table.rows(forSection: 0), 2) + XCTAssertEqual(table.rows(forSection: 1), 1) + } + + func testTableViewUpdate_OneSection() { + let table = TableView() + + table.register(cells: [ + TableTestHelper.InfoCell.self, + ]) + + table.append { + [ + [TableTestHelper.InfoData(title: "First Info!", count: 01, bio: "This is the very first!")] + ] + } + + table + .update { data in + var data = data + + data[0] += [ + 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."), + TableTestHelper.InfoData(title: "2nd Section!", count: 2222, bio: "2") + ] + + return data + } + + XCTAssertEqual(table.sections(), 1) + XCTAssertEqual(table.rows(forSection: 0), 3) + } +} + +@available(iOS 9.0, *) +fileprivate class TableTestHelper { + struct InfoData { + let title: String + let count: Int + let bio: String + } + + class InfoCell: UITableViewCell { + let label: Label = Label("") + let detailLabel: Label = Label("") + let bioLabel: Label = Label("") + } +} + +@available(iOS 9.0, *) +extension TableTestHelper.InfoData: CellDisplayable { + var cellID: String { + TableTestHelper.InfoCell.ID + } +} + +@available(iOS 9.0, *) +extension TableTestHelper.InfoCell: TableViewCell { + static var ID: String { + "InfoCell" + } + + func update(forData data: CellDisplayable) { + guard let data = data as? TableTestHelper.InfoData else { + return + } + + label.text = "SomeCell! \(data.title)" + detailLabel.text = "\(data.count)" + bioLabel.text = data.bio + } + + func configure(forData data: CellDisplayable) { + guard contentView.allSubviews.count == 0 else { + return + } + + contentView + .clear() + .embed { + VStack { + [ + HStack { + [ + label, + Spacer(), + detailLabel + ] + } + .padding(16), + bioLabel + .number(ofLines: 5) + ] + } + } + } +} diff --git a/Tests/SwiftUIKitTests/XCTestManifests.swift b/Tests/SwiftUIKitTests/XCTestManifests.swift index 9c40b0a..30c9364 100644 --- a/Tests/SwiftUIKitTests/XCTestManifests.swift +++ b/Tests/SwiftUIKitTests/XCTestManifests.swift @@ -3,7 +3,9 @@ import XCTest #if !canImport(ObjectiveC) public func allTests() -> [XCTestCaseEntry] { return [ - testCase(SwiftUIKitTests.allTests), + testCase(BasicSwiftUIKitTests.allTests), + testCase(BasicADATests.allTests), + testCase(NSMutableAttributedStringTests.allTests), ] } #endif