Skip to content

Commit fd22fb6

Browse files
committed
Added more views and extensions.
1 parent f07747c commit fd22fb6

22 files changed

+834
-78
lines changed

Package.swift

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import PackageDescription
55
let package = Package(
66
name: "SwiftUIExtension",
77
platforms: [
8-
.iOS(.v14),
8+
.iOS(.v15),
99
.watchOS(.v6),
1010
.macOS(.v10_15)
1111
],
@@ -16,13 +16,15 @@ let package = Package(
1616
)
1717
],
1818
dependencies: [
19-
.package(url: "https://github.com/sparrowcode/SwiftBoost", .upToNextMajor(from: "4.0.8"))
19+
.package(url: "https://github.com/sparrowcode/SwiftBoost", .upToNextMajor(from: "4.0.8")),
20+
.package(url: "https://github.com/siteline/swiftui-introspect", .upToNextMajor(from: "1.2.0"))
2021
],
2122
targets: [
2223
.target(
2324
name: "SwiftUIExtension",
2425
dependencies: [
25-
.product(name: "SwiftBoost", package: "SwiftBoost")
26+
.product(name: "SwiftBoost", package: "SwiftBoost"),
27+
.product(name: "SwiftUIIntrospect", package: "swiftui-introspect")
2628
],
2729
swiftSettings: [
2830
.define("SWIFTUIEXTENSION_SPM")

Sources/SwiftUIExtension/Compability/ViewCompability.swift

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,22 @@ import SwiftUI
22

33
extension View {
44

5+
public func scrollTargetBehaviorCompability() -> some View {
6+
if #available(iOS 17.0, watchOS 10.0, macOS 14.0, *) {
7+
return self.scrollTargetBehavior(.viewAligned)
8+
} else {
9+
return self
10+
}
11+
}
12+
13+
public func baselineOffsetCompability(_ offeset: CGFloat) -> some View {
14+
if #available(iOS 16.0, watchOS 9.0, macOS 13.0, *) {
15+
return self.baselineOffset(offeset)
16+
} else {
17+
return self
18+
}
19+
}
20+
521
public func fontWeightCompability(_ weight: Font.Weight) -> some View {
622
if #available(iOS 16.0, watchOS 9.0, macOS 13.0, *) {
723
return self.fontWeight(weight)
Lines changed: 80 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,89 @@
11
import SwiftUI
22
import SwiftBoost
33

4-
extension View {
4+
extension Color {
5+
6+
public init(hex string: String) {
7+
var string: String = string.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
8+
if string.hasPrefix("#") {
9+
_ = string.removeFirst()
10+
}
11+
12+
// Double the last value if incomplete hex
13+
if !string.count.isMultiple(of: 2), let last = string.last {
14+
string.append(last)
15+
}
16+
17+
// Fix invalid values
18+
if string.count > 8 {
19+
string = String(string.prefix(8))
20+
}
21+
22+
// Scanner creation
23+
let scanner = Scanner(string: string)
24+
25+
var color: UInt64 = 0
26+
scanner.scanHexInt64(&color)
27+
28+
if string.count == 2 {
29+
let mask = 0xFF
30+
31+
let g = Int(color) & mask
32+
33+
let gray = Double(g) / 255.0
34+
35+
self.init(.sRGB, red: gray, green: gray, blue: gray, opacity: 1)
36+
37+
} else if string.count == 4 {
38+
let mask = 0x00FF
39+
40+
let g = Int(color >> 8) & mask
41+
let a = Int(color) & mask
42+
43+
let gray = Double(g) / 255.0
44+
let alpha = Double(a) / 255.0
45+
46+
self.init(.sRGB, red: gray, green: gray, blue: gray, opacity: alpha)
47+
48+
} else if string.count == 6 {
49+
let mask = 0x0000FF
50+
let r = Int(color >> 16) & mask
51+
let g = Int(color >> 8) & mask
52+
let b = Int(color) & mask
53+
54+
let red = Double(r) / 255.0
55+
let green = Double(g) / 255.0
56+
let blue = Double(b) / 255.0
57+
58+
self.init(.sRGB, red: red, green: green, blue: blue, opacity: 1)
59+
60+
} else if string.count == 8 {
61+
let mask = 0x000000FF
62+
let r = Int(color >> 24) & mask
63+
let g = Int(color >> 16) & mask
64+
let b = Int(color >> 8) & mask
65+
let a = Int(color) & mask
66+
67+
let red = Double(r) / 255.0
68+
let green = Double(g) / 255.0
69+
let blue = Double(b) / 255.0
70+
let alpha = Double(a) / 255.0
71+
72+
self.init(.sRGB, red: red, green: green, blue: blue, opacity: alpha)
73+
74+
} else {
75+
self.init(.sRGB, red: 1, green: 1, blue: 1, opacity: 1)
76+
}
77+
}
78+
}
579

6-
#if canImport(UIKit)
80+
81+
extension View {
82+
83+
#if canImport(UIKit)
784
@available(iOS 15.0, watchOS 8.0, *)
885
public func foregroundColor(_ color: UIColor) -> some View {
986
self.foregroundColor(.init(uiColor: color))
1087
}
11-
#endif
88+
#endif
1289
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import Foundation
2+
import SwiftUI
3+
4+
extension Image {
5+
6+
public static func load(url: URL, completion: @escaping (Image?) -> Void) {
7+
DispatchQueue.global(qos: .background).async {
8+
if let data = try? Data(contentsOf: url) {
9+
10+
#if canImport(UIKit)
11+
let songArtwork = UIImage(data: data) ?? UIImage()
12+
completion(Image(uiImage: songArtwork))
13+
#endif
14+
15+
#if canImport(AppKit)
16+
let songArtwork = NSImage(data: data) ?? NSImage()
17+
completion(Image(nsImage: songArtwork))
18+
#endif
19+
20+
completion(nil)
21+
}
22+
}
23+
}
24+
}

Sources/SwiftUIExtension/Extensions/PreviewDevice.swift

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,19 @@ import SwiftUI
22

33
extension PreviewDevice {
44

5+
public static var iPhone16Pro: PreviewDevice {
6+
PreviewDevice(rawValue: "iPhone 16 Pro")
7+
}
8+
9+
public static var iPadPro11: PreviewDevice {
10+
PreviewDevice(rawValue: "iPad Pro 11-inch (M4)")
11+
}
12+
513
public static var visionPro: PreviewDevice {
614
PreviewDevice(rawValue: "Apple Vision Pro")
715
}
16+
17+
public static var mac: PreviewDevice {
18+
PreviewDevice(rawValue: "Mac")
19+
}
820
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import SwiftUI
2+
3+
extension View {
4+
5+
/**
6+
Default value for `safeAreaInset` none zero — this wrapper drop all spaces.
7+
*/
8+
public func safeAreaInsetNoneSpaced<Content: View>(edge: VerticalEdge, @ViewBuilder content: () -> Content) -> some View {
9+
self
10+
.safeAreaInset(edge: edge, spacing: .zero) {
11+
content()
12+
}
13+
}
14+
}
15+
16+
// MARK: - Environment
17+
18+
extension EnvironmentValues {
19+
20+
public var safeAreaInsets: EdgeInsets {
21+
self[SafeAreaInsetsKey.self]
22+
}
23+
}
24+
25+
private struct SafeAreaInsetsKey: EnvironmentKey {
26+
27+
static var defaultValue: EdgeInsets {
28+
(UIApplication.shared.windows.first(where: { $0.isKeyWindow })?.safeAreaInsets ?? .zero).insets
29+
}
30+
}
31+
32+
private extension UIEdgeInsets {
33+
34+
var insets: EdgeInsets {
35+
EdgeInsets(top: top, leading: left, bottom: bottom, trailing: right)
36+
}
37+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import SwiftUI
2+
3+
public struct SizeTracker: ViewModifier {
4+
5+
@Binding var size: CGSize
6+
7+
public func body(content: Content) -> some View {
8+
content.background(
9+
GeometryReader { geometry in
10+
Color.clear
11+
.onAppear {
12+
self.size = geometry.size
13+
}
14+
.onChange(of: geometry.size) { newSize in
15+
self.size = newSize
16+
}
17+
}
18+
)
19+
}
20+
}
21+
22+
extension View {
23+
24+
public func sizeChanged(_ size: Binding<CGSize>) -> some View {
25+
self.modifier(SizeTracker(size: size))
26+
}
27+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import SwiftUI
2+
3+
extension View {
4+
5+
public func fade(top: CGFloat? = nil, bottom: CGFloat? = nil) -> some View {
6+
modifier(VerticalFade(top: top, bottom: bottom))
7+
}
8+
}
9+
10+
struct VerticalFade: ViewModifier {
11+
12+
let top: CGFloat?
13+
let bottom: CGFloat?
14+
15+
func body(content: Content) -> some View {
16+
content
17+
.mask {
18+
VStack(spacing: .zero) {
19+
20+
if let top {
21+
// Top Fade
22+
LinearGradient(colors: [Color.black.opacity(0), Color.black], startPoint: .top, endPoint: .bottom)
23+
.frame(height: top)
24+
}
25+
26+
// Middle
27+
Rectangle()
28+
.fill(Color.black)
29+
.ignoresSafeArea()
30+
31+
if let bottom {
32+
// Bottom Fade
33+
LinearGradient(colors: [Color.black, Color.black.opacity(0)], startPoint: .top, endPoint: .bottom)
34+
.frame(height: bottom)
35+
}
36+
}
37+
}
38+
}
39+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import SwiftUI
2+
3+
extension View {
4+
5+
@ViewBuilder public func `if`<Content: View>(_ condition: Bool, transform: (Self) -> Content) -> some View {
6+
if condition {
7+
transform(self)
8+
} else {
9+
self
10+
}
11+
}
12+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import SwiftUI
2+
3+
extension View {
4+
5+
public func horizontalSystemPadding(to view: HorizontalSystemPadding.PaddingView) -> some View {
6+
modifier(HorizontalSystemPadding(paddingView: view))
7+
}
8+
9+
public func readableMargins() -> some View {
10+
self
11+
.padding(.horizontal)
12+
.frame(maxWidth: 414)
13+
}
14+
}
15+
16+
public struct HorizontalSystemPadding: ViewModifier {
17+
18+
let paddingView: PaddingView
19+
20+
var value: CGFloat {
21+
switch horizontalSizeClass {
22+
case .compact:
23+
return 16
24+
case .regular:
25+
#if os(visionOS)
26+
return 24
27+
#else
28+
return 20
29+
#endif
30+
default:
31+
return 16
32+
}
33+
}
34+
35+
@Environment(\.horizontalSizeClass) var horizontalSizeClass
36+
37+
public func body(content: Content) -> some View {
38+
switch paddingView {
39+
case .scroll:
40+
if #available(iOS 17.0, macOS 14.0, watchOS 10.0, *) {
41+
content
42+
.contentMargins(.horizontal, value, for: .scrollContent)
43+
} else {
44+
content
45+
}
46+
case .view:
47+
content.padding(.horizontal, value)
48+
}
49+
}
50+
51+
public enum PaddingView {
52+
53+
case scroll
54+
case view
55+
}
56+
}

0 commit comments

Comments
 (0)