Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add addGradient(colors:locations:direction:) extension #1039

Merged
merged 25 commits into from
Jun 19, 2023
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
c4704b6
Added `applyGradient(colors:locations:direction:)` extension to apply…
Andy0570 Aug 25, 2022
430cfd5
Merge branch 'SwifterSwift:master' into master
Andy0570 Sep 8, 2022
0614d99
Update Sources/SwifterSwift/UIKit/UIViewExtensions.swift
Andy0570 Nov 18, 2022
5235a19
Update Sources/SwifterSwift/UIKit/UIViewExtensions.swift
Andy0570 Nov 18, 2022
4356d1a
Update Sources/SwifterSwift/UIKit/UIViewExtensions.swift
Andy0570 Nov 18, 2022
862a926
Update Sources/SwifterSwift/UIKit/UIViewExtensions.swift
Andy0570 Nov 18, 2022
925b56a
Merge branch 'master' into master
Andy0570 Nov 18, 2022
56ab423
Merge branch 'master' of https://github.com/SwifterSwift/SwifterSwift
Andy0570 Nov 18, 2022
bb89a79
Merge branch 'master' of https://github.com/Andy0570/SwifterSwift
Andy0570 Nov 18, 2022
e053eab
Added a CHANGELOG entry.
Andy0570 Nov 18, 2022
e6bd261
added tests for UIView applyGradient()
pzarfostph Dec 23, 2022
c824866
Merge pull request #1 from pzarfos/master
Andy0570 Jan 4, 2023
5a43aa9
Merge branch 'master' into master
Andy0570 Jan 6, 2023
18857c7
Merge branch 'master' into master
Andy0570 Mar 6, 2023
43784d9
Update Sources/SwifterSwift/UIKit/UIViewExtensions.swift
Andy0570 Mar 6, 2023
8db3723
Update Sources/SwifterSwift/UIKit/UIViewExtensions.swift
Andy0570 Mar 6, 2023
d85b67b
update 'addGradient(colors:locations:direction:)'
Andy0570 Mar 6, 2023
a6038ee
rename 'applyGradient()' to 'addGradient()'
Andy0570 Mar 6, 2023
02f6f8a
Merge branch 'master' into master
Andy0570 Mar 7, 2023
2a6e7ed
Update CHANGELOG.md
Andy0570 Mar 20, 2023
26c1673
Refactor 'GradientDirection' type from enum to struct.
Andy0570 Mar 22, 2023
1b616a7
Merge branch 'master' into master
Andy0570 Apr 8, 2023
2365ea5
Merge branch 'master' into master
Andy0570 Apr 13, 2023
ddf32b4
Merge branch 'master' into master
guykogus Jun 19, 2023
05626ff
Merge branch 'master' into master
guykogus Jun 19, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 3 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,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]
Expand Down Expand Up @@ -468,8 +469,8 @@ 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**:
- `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)
- **UIRefreshControl**:
- `beginRefresh(in tableView:, animated:, sendAction:)` UIRefreshControl extension to begin refresh programatically. [#525](https://github.com/SwifterSwift/SwifterSwift/pull/525) by [ratulSharker](https://github.com/ratulSharker)
Andy0570 marked this conversation as resolved.
Show resolved Hide resolved
- **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).
- Added `mapKeysAndValues(_:)` to map a `Dictionary` into a `Dictionary` with different (or same) `Key` and `Value` types. [#546](https://github.com/SwifterSwift/SwifterSwift/pull/546) by [guykogus](https://github.com/guykogus)
Expand Down
46 changes: 46 additions & 0 deletions Sources/SwifterSwift/UIKit/UIViewExtensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,27 @@ public extension UIView {
/// SwifterSwift: easeInOut animation.
case easeInOut
}

/// SwifterSwift: Apply gradient directions
enum GradientDirection {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm wondering if this should be a struct, e.g.

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
}

This way it can be customised to exact values.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added a computed property in GradientDirection enum to make it more 'exact', so there is no need to add an extra struct to achieve this.

enum GradientDirection {
    case topToBottom
    case bottomToTop
    case leftToRight
    case rightToLeft

    var gradientEndpoint: (CGPoint, CGPoint) {
        switch self {
        case .topToBottom:
            return (CGPoint(x: 0.5, y: 0.0), CGPoint(x: 0.5, y: 1.0))
        case .bottomToTop:
            return (CGPoint(x: 0.5, y: 1.0), CGPoint(x: 0.5, y: 0.0))
        case .leftToRight:
            return (CGPoint(x: 0.0, y: 0.5), CGPoint(x: 1.0, y: 0.5))
        case .rightToLeft:
            return (CGPoint(x: 1.0, y: 0.5), CGPoint(x: 0.0, y: 0.5))
        }
    }
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But what if somebody wants to customise the direction? This is limiting.
Also, you've used a tuple instead of the struct, which is harder to read when using and is doing the same thing.

Copy link
Member Author

@Andy0570 Andy0570 Mar 24, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have updated to be more flexible as you say. please check change.

case topToBottom
case bottomToTop
case leftToRight
case rightToLeft

var gradientEndpoint: (CGPoint, CGPoint) {
switch self {
case .topToBottom:
return (CGPoint(x: 0.5, y: 0.0), CGPoint(x: 0.5, y: 1.0))
case .bottomToTop:
return (CGPoint(x: 0.5, y: 1.0), CGPoint(x: 0.5, y: 0.0))
case .leftToRight:
return (CGPoint(x: 0.0, y: 0.5), CGPoint(x: 1.0, y: 0.5))
case .rightToLeft:
return (CGPoint(x: 1.0, y: 0.5), CGPoint(x: 0.0, y: 0.5))
}
}
}
}

// MARK: - Properties
Expand Down Expand Up @@ -472,6 +493,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: Enumeration type describing the direction of the gradient.
func addGradient(colors: [SFColor], locations: [CGFloat] = [0.0, 1.0], direction: GradientDirection = .topToBottom) {
// <https://github.com/swiftdevcenter/GradientColorExample>
let gradientLayer = CAGradientLayer()
gradientLayer.frame = bounds
gradientLayer.colors = colors.map { $0.cgColor }
gradientLayer.locations = locations.map { NSNumber(value: $0) }
gradientLayer.startPoint = direction.gradientEndpoint.0
gradientLayer.endPoint = direction.gradientEndpoint.1
layer.addSublayer(gradientLayer)
}

/// SwifterSwift: Add Visual Format constraints.
///
/// - Parameters:
Expand Down
118 changes: 118 additions & 0 deletions Tests/UIKitTests/UIViewExtensionsTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down