Skip to content

Commit

Permalink
SnapshotBorder (#58)
Browse files Browse the repository at this point in the history
* String.width

* Text.foregroundLinearGradient

* View.border

* snapshotTestBorder

---------

Co-authored-by: Oguz Yuksel <[email protected]>
  • Loading branch information
OguzYuuksel and Oguz Yuksel authored May 15, 2023
1 parent 407b092 commit b3eb108
Show file tree
Hide file tree
Showing 5 changed files with 219 additions and 0 deletions.
1 change: 1 addition & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ let package = Package(
name: "SnapshotTestingExtensions",
dependencies: [
.product(name: "SnapshotTesting", package: "swift-snapshot-testing"),
"UIExtensions"
]
),
.testTarget(name: "UIExtensionsTests",
Expand Down
68 changes: 68 additions & 0 deletions Sources/SnapshotTestingExtensions/Extensions/SwiftUI/View.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// Copyright © 2023 Lautsprecher Teufel GmbH. All rights reserved.

#if DEBUG && canImport(SwiftUI)
import SwiftUI
import UIExtensions

// MARK: - snapshotTestBorder
extension View {
/// This function allows you to add a custom border to a view for snapshot testing purposes.
///
/// - Parameters:
/// - title: A string that will be used to generate the repeated title inside the border. Defaults to "SnapshotTest".
///
/// - Returns: A modified version of the view that has a custom border with a repeated title inside it.
public func snapshotTestBorder(_ title: String = "SnapshotTest") -> some View {
modifier(SnapshotTestBorderViewModifier(title))
}
}

struct SnapshotTestBorderViewModifier: ViewModifier {
@State
private var contentSize: CGSize = .zero
private var fontSize: CGFloat {
min(contentSize.width, contentSize.height) * 0.1
}
private var repeatedTitle: String {
var mutableRepeatedTitle = title
while max(contentSize.height, contentSize.width)
> mutableRepeatedTitle.width(for: .systemFont(ofSize: fontSize)) {
mutableRepeatedTitle.append(title)
}
return mutableRepeatedTitle
}
private let title: String

init(_ title: String) {
self.title = title
}

func body(content: Content) -> some View {
content
.measureView { contentSize = $0 }
.border(
{
Text(repeatedTitle)
.font(.system(size: fontSize))
.foregroundLinearGradient(
[
.red,
.orange,
.yellow,
.green,
.init(red: 63, green: 0, blue: 255),
.purple
],
startPoint: .topLeading,
endPoint: .bottomTrailing
)
.lineLimit(1)
.truncationMode(.middle)
.multilineTextAlignment(.center)
},
dividerColor: .red,
dividerWidth: 0.4
)
}
}
#endif
29 changes: 29 additions & 0 deletions Sources/UIExtensions/Extensions/String+Extensions.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// Copyright © 2023 Lautsprecher Teufel GmbH. All rights reserved.

#if canImport(UIKit)
import UIKit
extension String {
/**
The extension provides a convenient way to calculate the width of a given string when rendered with a specific font. This is particularly useful for dynamically sizing UI elements such as labels or text fields based on their content.

To use this extension, simply call the `width(for:)` method on any string instance and pass in the desired font as an argument. The method will return the calculated width of the string as a `CGFloat` value.

Example Usage:

let myString = "Hello, World!"
let myFont = UIFont.systemFont(ofSize: 18)
let stringWidth = myString.width(for: myFont)
print(stringWidth)

This will output the calculated width of the string "Hello, World!" when rendered with the system font at size 18.

- Parameter font: The font to be used for rendering the string.
- Returns: The width of the string when rendered with the given font.
*/
public func width(for font: UIFont) -> CGFloat {
let fontAttributes = [NSAttributedString.Key.font: font]
let size = self.size(withAttributes: fontAttributes)
return size.width
}
}
#endif
45 changes: 45 additions & 0 deletions Sources/UIExtensions/Extensions/Text+Extensions.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// Copyright © 2023 Lautsprecher Teufel GmbH. All rights reserved.

#if canImport(SwiftUI)
import SwiftUI
extension Text {
/**
The extension provides a way to apply a linear gradient as the foreground of a text view. This is useful for creating visually interesting text with gradients.

To use this extension, call the `foregroundLinearGradient(colors:startPoint:endPoint:)` method on any Text view instance. Pass in an array of `Color` values to be used in the gradient, as well as the start and end points of the gradient, specified as `UnitPoint` values.

Example Usage:

Text("Hello, World!")
.font(.title)
.foregroundColor(.white)
.foregroundLinearGradient(
colors: [.red, .yellow],
startPoint: .leading,
endPoint: .trailing
)

This will apply a linear gradient from red to yellow as the foreground of the "Hello, World!" text. The gradient will start at the leading edge of the text and end at the trailing edge.

- Parameters:
- colors: An array of colors to be used in the gradient.
- startPoint: The point at which the gradient begins. The point is specified as a unit coordinate space.
- endPoint: The point at which the gradient ends. The point is specified as a unit coordinate space.
- Returns: A view with a linear gradient as its foreground.
*/
public func foregroundLinearGradient(
_ colors: [Color],
startPoint: UnitPoint,
endPoint: UnitPoint
) -> some View {
self.overlay(
LinearGradient(
colors: colors,
startPoint: startPoint,
endPoint: endPoint
)
.mask(self)
)
}
}
#endif
76 changes: 76 additions & 0 deletions Sources/UIExtensions/Extensions/View+Extensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,82 @@ extension View {
}
}

// MARK: - SquaredBorder
extension View {
/**
The extension adds a squared border to a view and displays the provided `CustomText` view on each side of the border.
This is useful for displaying annotations or labels for the content inside the border.

To use this extension, call the `border(_:dividerColor:dividerWidth:)` method on any `View` instance.
Pass in a closure that returns a `CustomText` view to be displayed on each side

- Parameters:
- text: A closure that returns a `CustomText` view to be displayed on each side of the border.
- dividerColor: The color of the border. Default value is `.red`.
- dividerWidth: The width of the border. Default value is `0.4`.
*/
public func border<CustomText: View>(
@ViewBuilder
_ text: @escaping () -> CustomText,
dividerColor: Color = .red,
dividerWidth: CGFloat = 0.4
) -> some View {
modifier(SquaredBorderTextViewModifier(
text,
dividerColor: dividerColor,
dividerWidth: dividerWidth)
)
}
}

private struct SquaredBorderTextViewModifier<CustomText: View>: ViewModifier {
@State
private var contentSize: CGSize = .zero
@State
private var textSize: CGSize = .zero
@ViewBuilder
private let text: () -> CustomText
private let dividerColor: Color
private let dividerWidth: CGFloat

init(
@ViewBuilder _ text: @escaping () -> CustomText,
dividerColor: Color,
dividerWidth: CGFloat
) {
self.text = text
self.dividerColor = dividerColor
self.dividerWidth = dividerWidth
}

func body(content: Content) -> some View {
HStack(spacing: .zero) {
text()
.frame(width: contentSize.height)
.rotationEffect(.degrees(270))
.frame(width: textSize.height, height: contentSize.height)
VStack(spacing: .zero) {
text()
.measureView { textSize = $0 }
.frame(width: contentSize.width)
content
.measureView { contentSize = $0 }
.padding(dividerWidth)
.border(dividerColor, width: dividerWidth)
text()
.frame(width: contentSize.width)
.rotationEffect(.degrees(180))
}
text()
.frame(width: contentSize.height)
.rotationEffect(.degrees(90))
.frame(width: textSize.height, height: contentSize.height)
}
.padding(dividerWidth)
.border(dividerColor, width: dividerWidth)
}
}

// MARK: - Navigation
extension View {
@available(iOS, introduced: 15)
Expand Down

0 comments on commit b3eb108

Please sign in to comment.