diff --git a/CHANGELOG.md b/CHANGELOG.md index 834f5bb86..3ccd5756c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -93,6 +93,7 @@ The changelog for **SwifterSwift**. Also see the [releases](https://github.com/S - Added `widthConstraint`, `heightConstraint`, `leadingConstraint`, `trailingConstraint`, `topConstraint`, and `bottomConstraint` for finding specific constraints. [#886](https://github.com/SwifterSwift/SwifterSwift/pull/886) by [gurgeous](https://github.com/gurgeous) - Added `UIView.subviews(ofType:)` extension which returns all the subviews of a given type recursively in the view hierarchy rooted on the view it its called. [#993](https://github.com/SwifterSwift/SwifterSwift/pull/993) by [ashercoelho](https://github.com/ashercoelho) - Added `UIStackView.swap(_ view1:, _ view2:)` extension which exchanges two views that are arranged in the stack. [#989](https://github.com/SwifterSwift/SwifterSwift/pull/989) by [salahamassi](https://github.com/salahamassi) + - Added `addGradient(colors:locations:direction:)` extension to apply gradient color. [#1039](https://github.com/SwifterSwift/SwifterSwift/pull/1039) by [Andy0570](https://github.com/Andy0570) - **UIImage** - Added `averageColor`, which calculates the average UIColor for an entire image. [#884](https://github.com/SwifterSwift/SwifterSwift/pull/884) by [gurgeous](https://github.com/gurgeous) - Added `withAlwaysOriginalTintColor(_:)` returns a new version of the image with a tint color that uses the .alwaysOriginal rendering mode. [#886](https://github.com/SwifterSwift/SwifterSwift/pull/886) by [jayxiang][https://github.com/jayxiang] @@ -472,7 +473,7 @@ The changelog for **SwifterSwift**. Also see the [releases](https://github.com/S - Added scalar multiplication of CGFloat and CGVector via standard multiplication operator (\*). [#527](https://github.com/SwifterSwift/SwifterSwift/pull/527) by [moyerr](https://github.com/moyerr) - Added negation of vectors via prefix (-) operator. [#527](https://github.com/SwifterSwift/SwifterSwift/pull/527) by [moyerr](https://github.com/moyerr) - Added `init(angle:magnitude:)` to create vectors based on their angle and magnitude. [#527](https://github.com/SwifterSwift/SwifterSwift/pull/527) by [moyerr](https://github.com/moyerr) --**UIRefreshControl**: +- **UIRefreshControl**: - `beginRefresh(in tableView:, animated:, sendAction:)` UIRefreshControl extension to begin refresh programmatically. [#525](https://github.com/SwifterSwift/SwifterSwift/pull/525) by [ratulSharker](https://github.com/ratulSharker) - **Dictionary**: - Added `removeValueForRandomKey()` to remove a value for a random key from a dictionary. [#497](https://github.com/SwifterSwift/SwifterSwift/pull/497) by [MaxHaertwig](https://github.com/maxhaertwig). diff --git a/Sources/SwifterSwift/UIKit/UIViewExtensions.swift b/Sources/SwifterSwift/UIKit/UIViewExtensions.swift index 85ebc4bea..1c37fabf3 100755 --- a/Sources/SwifterSwift/UIKit/UIViewExtensions.swift +++ b/Sources/SwifterSwift/UIKit/UIViewExtensions.swift @@ -49,6 +49,21 @@ public extension UIView { /// SwifterSwift: easeInOut animation. case easeInOut } + + /// SwifterSwift: Add gradient directions + struct GradientDirection { + static let topToBottom = GradientDirection(startPoint: CGPoint(x: 0.5, y: 0.0), + endPoint: CGPoint(x: 0.5, y: 1.0)) + static let bottomToTop = GradientDirection(startPoint: CGPoint(x: 0.5, y: 1.0), + endPoint: CGPoint(x: 0.5, y: 0.0)) + static let leftToRight = GradientDirection(startPoint: CGPoint(x: 0.0, y: 0.5), + endPoint: CGPoint(x: 1.0, y: 0.5)) + static let rightToLeft = GradientDirection(startPoint: CGPoint(x: 1.0, y: 0.5), + endPoint: CGPoint(x: 0.0, y: 0.5)) + + let startPoint: CGPoint + let endPoint: CGPoint + } } // MARK: - Properties @@ -472,6 +487,31 @@ public extension UIView { CATransaction.commit() } + /// SwifterSwift: Add Gradient Colors. + /// + /// view.addGradient( + /// colors: [.red, .blue], + /// locations: [0.0, 1.0], + /// direction: .topToBottom + /// ) + /// + /// - Parameters: + /// - colors: An array of `SFColor` defining the color of each gradient stop. + /// - locations: An array of `CGFloat` defining the location of each + /// gradient stop as a value in the range [0, 1]. The values must be + /// monotonically increasing. + /// - direction: Struct type describing the direction of the gradient. + func addGradient(colors: [SFColor], locations: [CGFloat] = [0.0, 1.0], direction: GradientDirection) { + // + let gradientLayer = CAGradientLayer() + gradientLayer.frame = bounds + gradientLayer.colors = colors.map { $0.cgColor } + gradientLayer.locations = locations.map { NSNumber(value: $0) } + gradientLayer.startPoint = direction.startPoint + gradientLayer.endPoint = direction.endPoint + layer.addSublayer(gradientLayer) + } + /// SwifterSwift: Add Visual Format constraints. /// /// - Parameters: diff --git a/Tests/UIKitTests/UIViewExtensionsTests.swift b/Tests/UIKitTests/UIViewExtensionsTests.swift index be45b1df7..4b0d55281 100644 --- a/Tests/UIKitTests/UIViewExtensionsTests.swift +++ b/Tests/UIKitTests/UIViewExtensionsTests.swift @@ -378,6 +378,124 @@ final class UIViewExtensionsTests: XCTestCase { XCTAssert(view.gestureRecognizers!.isEmpty) } + func testAddGradient() { + // topToBottom + let view0 = UIView() + XCTAssertNil(view0.layer.sublayers) + view0.addGradient( + colors:[.red, .orange, .green, .blue], + locations: [0.0, 0.333, 0.667, 1.0], + direction: .topToBottom + ) + XCTAssertNotNil(view0.layer.sublayers) + if let sublayers = view0.layer.sublayers as? [CAGradientLayer] { + XCTAssertEqual(sublayers.count, 1) + XCTAssertTrue(sublayers[0].startPoint.x.isEqual(to: 0.5)) + XCTAssertTrue(sublayers[0].startPoint.y.isEqual(to: 0.0)) + XCTAssertTrue(sublayers[0].endPoint.x.isEqual(to: 0.5)) + XCTAssertTrue(sublayers[0].endPoint.y.isEqual(to: 1.0)) + XCTAssertEqual(sublayers[0].colors?.count, 4) + // swiftlint:disable force_cast + XCTAssertEqual(sublayers[0].colors?[0] as! CGColor, UIColor.red.cgColor) + XCTAssertEqual(sublayers[0].colors?[1] as! CGColor, UIColor.orange.cgColor) + XCTAssertEqual(sublayers[0].colors?[2] as! CGColor, UIColor.green.cgColor) + XCTAssertEqual(sublayers[0].colors?[3] as! CGColor, UIColor.blue.cgColor) + // swiftlint:enable force_cast + XCTAssertEqual(sublayers[0].locations?.count, 4) + XCTAssertNotNil(sublayers[0].locations?[0].isEqual(to: 0.0)) + XCTAssertNotNil(sublayers[0].locations?[1].isEqual(to: 0.333)) + XCTAssertNotNil(sublayers[0].locations?[2].isEqual(to: 0.667)) + XCTAssertNotNil(sublayers[0].locations?[3].isEqual(to: 1.0)) + } + + // bottomToTop + let view1 = UIView() + XCTAssertNil(view1.layer.sublayers) + view1.addGradient( + colors: [.red, .orange, .green, .blue], + locations: [0.0, 0.333, 0.667, 1.0], + direction: .bottomToTop + ) + XCTAssertNotNil(view1.layer.sublayers) + if let sublayers = view1.layer.sublayers as? [CAGradientLayer] { + XCTAssertEqual(sublayers.count, 1) + XCTAssertTrue(sublayers[0].startPoint.x.isEqual(to: 0.5)) + XCTAssertTrue(sublayers[0].startPoint.y.isEqual(to: 1.0)) + XCTAssertTrue(sublayers[0].endPoint.x.isEqual(to: 0.5)) + XCTAssertTrue(sublayers[0].endPoint.y.isEqual(to: 0.0)) + XCTAssertEqual(sublayers[0].colors?.count, 4) + // swiftlint:disable force_cast + XCTAssertEqual(sublayers[0].colors?[0] as! CGColor, UIColor.red.cgColor) + XCTAssertEqual(sublayers[0].colors?[1] as! CGColor, UIColor.orange.cgColor) + XCTAssertEqual(sublayers[0].colors?[2] as! CGColor, UIColor.green.cgColor) + XCTAssertEqual(sublayers[0].colors?[3] as! CGColor, UIColor.blue.cgColor) + // swiftlint:enable force_cast + XCTAssertEqual(sublayers[0].locations?.count, 4) + XCTAssertNotNil(sublayers[0].locations?[0].isEqual(to: 0.0)) + XCTAssertNotNil(sublayers[0].locations?[1].isEqual(to: 0.333)) + XCTAssertNotNil(sublayers[0].locations?[2].isEqual(to: 0.667)) + XCTAssertNotNil(sublayers[0].locations?[3].isEqual(to: 1.0)) + } + + // leftToRight + let view2 = UIView() + XCTAssertNil(view2.layer.sublayers) + view2.addGradient( + colors: [.red, .orange, .green, .blue], + locations: [0.0, 0.333, 0.667, 1.0], + direction: .leftToRight + ) + XCTAssertNotNil(view2.layer.sublayers) + if let sublayers = view2.layer.sublayers as? [CAGradientLayer] { + XCTAssertEqual(sublayers.count, 1) + XCTAssertTrue(sublayers[0].startPoint.x.isEqual(to: 0.0)) + XCTAssertTrue(sublayers[0].startPoint.y.isEqual(to: 0.5)) + XCTAssertTrue(sublayers[0].endPoint.x.isEqual(to: 1.0)) + XCTAssertTrue(sublayers[0].endPoint.y.isEqual(to: 0.5)) + XCTAssertEqual(sublayers[0].colors?.count, 4) + // swiftlint:disable force_cast + XCTAssertEqual(sublayers[0].colors?[0] as! CGColor, UIColor.red.cgColor) + XCTAssertEqual(sublayers[0].colors?[1] as! CGColor, UIColor.orange.cgColor) + XCTAssertEqual(sublayers[0].colors?[2] as! CGColor, UIColor.green.cgColor) + XCTAssertEqual(sublayers[0].colors?[3] as! CGColor, UIColor.blue.cgColor) + // swiftlint:enable force_cast + XCTAssertEqual(sublayers[0].locations?.count, 4) + XCTAssertNotNil(sublayers[0].locations?[0].isEqual(to: 0.0)) + XCTAssertNotNil(sublayers[0].locations?[1].isEqual(to: 0.333)) + XCTAssertNotNil(sublayers[0].locations?[2].isEqual(to: 0.667)) + XCTAssertNotNil(sublayers[0].locations?[3].isEqual(to: 1.0)) + } + + // rightToLeft + let view3 = UIView() + XCTAssertNil(view3.layer.sublayers) + view3.addGradient( + colors: [.red, .orange, .green, .blue], + locations: [0.0, 0.333, 0.667, 1.0], + direction: .rightToLeft + ) + XCTAssertNotNil(view3.layer.sublayers) + if let sublayers = view3.layer.sublayers as? [CAGradientLayer] { + XCTAssertEqual(sublayers.count, 1) + XCTAssertTrue(sublayers[0].startPoint.x.isEqual(to: 1.0)) + XCTAssertTrue(sublayers[0].startPoint.y.isEqual(to: 0.5)) + XCTAssertTrue(sublayers[0].endPoint.x.isEqual(to: 0.0)) + XCTAssertTrue(sublayers[0].endPoint.y.isEqual(to: 0.5)) + XCTAssertEqual(sublayers[0].colors?.count, 4) + // swiftlint:disable force_cast + XCTAssertEqual(sublayers[0].colors?[0] as! CGColor, UIColor.red.cgColor) + XCTAssertEqual(sublayers[0].colors?[1] as! CGColor, UIColor.orange.cgColor) + XCTAssertEqual(sublayers[0].colors?[2] as! CGColor, UIColor.green.cgColor) + XCTAssertEqual(sublayers[0].colors?[3] as! CGColor, UIColor.blue.cgColor) + // swiftlint:enable force_cast + XCTAssertEqual(sublayers[0].locations?.count, 4) + XCTAssertNotNil(sublayers[0].locations?[0].isEqual(to: 0.0)) + XCTAssertNotNil(sublayers[0].locations?[1].isEqual(to: 0.333)) + XCTAssertNotNil(sublayers[0].locations?[2].isEqual(to: 0.667)) + XCTAssertNotNil(sublayers[0].locations?[3].isEqual(to: 1.0)) + } + } + func testAnchor() { let view = UIView() let subview = UIView()