Skip to content

Commit 4ffb807

Browse files
committed
initial commit
0 parents  commit 4ffb807

File tree

3,319 files changed

+63201
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

3,319 files changed

+63201
-0
lines changed

.gitignore

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
.DS_Store
2+
/.build
3+
/Packages
4+
xcuserdata/
5+
DerivedData/
6+
.swiftpm/configuration/registries.json
7+
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
8+
.netrc
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<Scheme
3+
LastUpgradeVersion = "2610"
4+
version = "1.7">
5+
<BuildAction
6+
parallelizeBuildables = "YES"
7+
buildImplicitDependencies = "YES"
8+
buildArchitectures = "Automatic">
9+
<BuildActionEntries>
10+
<BuildActionEntry
11+
buildForTesting = "YES"
12+
buildForRunning = "YES"
13+
buildForProfiling = "YES"
14+
buildForArchiving = "YES"
15+
buildForAnalyzing = "YES">
16+
<BuildableReference
17+
BuildableIdentifier = "primary"
18+
BlueprintIdentifier = "LucideSwiftUI"
19+
BuildableName = "LucideSwiftUI"
20+
BlueprintName = "LucideSwiftUI"
21+
ReferencedContainer = "container:">
22+
</BuildableReference>
23+
</BuildActionEntry>
24+
</BuildActionEntries>
25+
</BuildAction>
26+
<TestAction
27+
buildConfiguration = "Debug"
28+
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
29+
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
30+
shouldUseLaunchSchemeArgsEnv = "YES"
31+
shouldAutocreateTestPlan = "YES">
32+
</TestAction>
33+
<LaunchAction
34+
buildConfiguration = "Debug"
35+
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
36+
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
37+
launchStyle = "0"
38+
useCustomWorkingDirectory = "NO"
39+
ignoresPersistentStateOnLaunch = "NO"
40+
debugDocumentVersioning = "YES"
41+
debugServiceExtension = "internal"
42+
allowLocationSimulation = "YES">
43+
</LaunchAction>
44+
<ProfileAction
45+
buildConfiguration = "Release"
46+
shouldUseLaunchSchemeArgsEnv = "YES"
47+
savedToolIdentifier = ""
48+
useCustomWorkingDirectory = "NO"
49+
debugDocumentVersioning = "YES">
50+
<MacroExpansion>
51+
<BuildableReference
52+
BuildableIdentifier = "primary"
53+
BlueprintIdentifier = "LucideSwiftUI"
54+
BuildableName = "LucideSwiftUI"
55+
BlueprintName = "LucideSwiftUI"
56+
ReferencedContainer = "container:">
57+
</BuildableReference>
58+
</MacroExpansion>
59+
</ProfileAction>
60+
<AnalyzeAction
61+
buildConfiguration = "Debug">
62+
</AnalyzeAction>
63+
<ArchiveAction
64+
buildConfiguration = "Release"
65+
revealArchiveInOrganizer = "YES">
66+
</ArchiveAction>
67+
</Scheme>

Package.resolved

Lines changed: 15 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Package.swift

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// swift-tools-version: 6.1
2+
// The swift-tools-version declares the minimum version of Swift required to build this package.
3+
4+
import PackageDescription
5+
6+
let package = Package(
7+
name: "LucideSwiftUI",
8+
platforms: [
9+
.iOS(.v14),
10+
.macOS(.v11),
11+
.tvOS(.v14),
12+
.watchOS(.v7)
13+
],
14+
products: [
15+
// Products define the executables and libraries a package produces, making them visible to other packages.
16+
.library(
17+
name: "LucideSwiftUI",
18+
targets: ["LucideSwiftUI"]),
19+
],
20+
dependencies: [
21+
.package(url: "https://github.com/exyte/SVGView.git", from: "1.0.0")
22+
],
23+
targets: [
24+
// Targets are the basic building blocks of a package, defining a module or a test suite.
25+
// Targets can depend on other targets in this package and products from dependencies.
26+
.target(
27+
name: "LucideSwiftUI",
28+
dependencies: ["SVGView"],
29+
resources: [
30+
.process("icons")
31+
]),
32+
33+
]
34+
)

README.md

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
# LucideSwiftUI
2+
3+
SwiftUI wrapper for [Lucide icons](https://lucide.dev). Drop in 1500+ icons into your apps.
4+
5+
## Quick start
6+
7+
Add it via Swift Package Manager:
8+
9+
```swift
10+
dependencies: [
11+
.package(url: "https://github.com/harispdev/lucide-swiftui.git", from: "1.0.0")
12+
]
13+
```
14+
15+
Or in Xcode: `File -> Add Packages` and paste the repo URL.
16+
17+
Then use it:
18+
19+
```swift
20+
import LucideSwiftUI
21+
22+
LucideIcon(name: "heart", size: 32, color: .red)
23+
```
24+
25+
That's it. Works with any Lucide icon.
26+
27+
## Customization
28+
29+
You can tweak size, color, and stroke width:
30+
31+
```swift
32+
LucideIcon(
33+
name: "star",
34+
size: 48,
35+
color: .yellow,
36+
strokeWidth: 3.0
37+
)
38+
```
39+
40+
## Type-safe icons
41+
42+
For common icons, there's a type-safe enum to avoid typos:
43+
44+
```swift
45+
LucideIcon(icon: .heart, size: 32, color: .red)
46+
LucideIcon(icon: .star, size: 32, color: .yellow)
47+
LucideIcon(icon: .arrowDown, size: 32)
48+
```
49+
50+
The enum covers the most commonly used icons. For everything else, just use the string name, all the icons are available that way.
51+
52+
## Examples
53+
54+
Here's a simple icon list:
55+
56+
```swift
57+
HStack {
58+
LucideIcon(name: "heart", size: 24, color: .red)
59+
LucideIcon(name: "star", size: 24, color: .yellow)
60+
LucideIcon(name: "bookmark", size: 24, color: .blue)
61+
}
62+
```
63+
64+
Or use them in buttons:
65+
66+
```swift
67+
Button(action: { /* do something */ }) {
68+
LucideIcon(name: "download", size: 20, color: .blue)
69+
}
70+
```
71+
72+
## Requirements
73+
74+
- iOS 14.0+ / macOS 11.0+ / tvOS 14.0+ / watchOS 7.0+
75+
- Swift 5.5+
76+
77+
## Dependencies
78+
79+
This library uses [SVGView](https://github.com/exyte/SVGView) by Exyte for SVG rendering.
80+
81+
## License & attribution
82+
83+
This library is provided free of charge for personal and commercial use,
84+
subject to the following conditions:
85+
86+
1. You may not repackage, wrap, modify, rebrand, sublicense, or otherwise
87+
redistribute this library, in whole or in part, in a manner that represents
88+
the library as your own original work or obscures the authorship.
89+
90+
2. You may not distribute derivative works whose primary purpose is to provide
91+
this library or any substantial portion of it under a different name or
92+
identity.
93+
94+
3. Use of this library within a larger application or service is permitted,
95+
provided that no claim of authorship over the library itself is made.
96+
97+
4. Attribution to the original author must be retained in all copies or
98+
substantial portions of the library.
99+
100+
By using this library, you agree to be bound by these terms.
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
import SwiftUI
2+
import SVGView
3+
4+
public struct LucideIcon: View {
5+
let iconName: String
6+
let size: CGFloat
7+
let color: Color
8+
let strokeWidth: CGFloat
9+
let weight: String
10+
11+
public init(
12+
name: String,
13+
size: CGFloat = 24,
14+
color: Color = .primary,
15+
strokeWidth: CGFloat = 2.0,
16+
weight: String = "Regular-S"
17+
) {
18+
self.iconName = name
19+
self.size = size
20+
self.color = color
21+
self.strokeWidth = strokeWidth
22+
self.weight = weight
23+
}
24+
25+
private var svgURL: URL? {
26+
var url: URL?
27+
28+
url = Bundle.module.url(forResource: iconName, withExtension: "svg")
29+
30+
if url == nil {
31+
url = Bundle.module.url(forResource: iconName, withExtension: "svg", subdirectory: "icons")
32+
}
33+
34+
if url == nil, let bundlePath = Bundle.module.resourcePath {
35+
let iconsPath = (bundlePath as NSString).appendingPathComponent("icons")
36+
let filePath = (iconsPath as NSString).appendingPathComponent("\(iconName).svg")
37+
if FileManager.default.fileExists(atPath: filePath) {
38+
url = URL(fileURLWithPath: filePath)
39+
}
40+
}
41+
42+
if url == nil {
43+
url = Bundle.main.url(forResource: iconName, withExtension: "svg")
44+
}
45+
46+
if url == nil {
47+
for bundle in Bundle.allBundles {
48+
if let bundleUrl = bundle.url(forResource: iconName, withExtension: "svg") {
49+
url = bundleUrl
50+
break
51+
}
52+
}
53+
}
54+
55+
return url
56+
}
57+
58+
private var modifiedSVGContent: String? {
59+
guard let url = svgURL,
60+
let svgContent = try? String(contentsOf: url, encoding: .utf8) else {
61+
return nil
62+
}
63+
64+
let hexColor: String
65+
#if os(iOS) || os(tvOS) || os(watchOS)
66+
let uiColor = UIColor(color)
67+
var red: CGFloat = 0
68+
var green: CGFloat = 0
69+
var blue: CGFloat = 0
70+
var alpha: CGFloat = 0
71+
72+
if uiColor.getRed(&red, green: &green, blue: &blue, alpha: &alpha) {
73+
hexColor = String(format: "#%02X%02X%02X", Int(red * 255), Int(green * 255), Int(blue * 255))
74+
} else {
75+
if let rgbColor = uiColor.cgColor.converted(to: CGColorSpaceCreateDeviceRGB(), intent: .defaultIntent, options: nil) {
76+
if rgbColor.numberOfComponents >= 3 {
77+
red = rgbColor.components?[0] ?? 0
78+
green = rgbColor.components?[1] ?? 0
79+
blue = rgbColor.components?[2] ?? 0
80+
hexColor = String(format: "#%02X%02X%02X", Int(red * 255), Int(green * 255), Int(blue * 255))
81+
} else {
82+
hexColor = "#000000"
83+
}
84+
} else {
85+
hexColor = "#000000"
86+
}
87+
}
88+
#elseif os(macOS)
89+
let nsColor = NSColor(color)
90+
var red: CGFloat = 0
91+
var green: CGFloat = 0
92+
var blue: CGFloat = 0
93+
var alpha: CGFloat = 0
94+
95+
if let rgbColor = nsColor.usingColorSpace(.deviceRGB) {
96+
rgbColor.getRed(&red, green: &green, blue: &blue, alpha: &alpha)
97+
hexColor = String(format: "#%02X%02X%02X", Int(red * 255), Int(green * 255), Int(blue * 255))
98+
} else {
99+
hexColor = "#000000"
100+
}
101+
#else
102+
hexColor = "#000000"
103+
#endif
104+
105+
var modified = svgContent
106+
107+
modified = modified.replacingOccurrences(
108+
of: #"stroke\s*=\s*"[^"]*""#,
109+
with: #"stroke="\#(hexColor)""#,
110+
options: [.regularExpression, .caseInsensitive]
111+
)
112+
113+
modified = modified.replacingOccurrences(
114+
of: #"stroke-width\s*=\s*"[^"]*""#,
115+
with: #"stroke-width="\#(strokeWidth)""#,
116+
options: [.regularExpression, .caseInsensitive]
117+
)
118+
119+
if !modified.contains("stroke-width") {
120+
if let svgRange = modified.range(of: "<svg", options: .caseInsensitive) {
121+
let afterSvg = modified[svgRange.upperBound...]
122+
if let closingBracket = afterSvg.firstIndex(of: ">") {
123+
modified.insert(contentsOf: #" stroke-width="\#(strokeWidth)""#, at: closingBracket)
124+
}
125+
}
126+
}
127+
128+
modified = modified.replacingOccurrences(
129+
of: #"<path([^>]*)\s+stroke\s*=\s*"[^"]*"([^>]*)>"#,
130+
with: #"<path$1 stroke="\#(hexColor)"$2>"#,
131+
options: [.regularExpression, .caseInsensitive]
132+
)
133+
134+
modified = modified.replacingOccurrences(
135+
of: #"<path([^>]*)\s+stroke-width\s*=\s*"[^"]*"([^>]*)>"#,
136+
with: #"<path$1 stroke-width="\#(strokeWidth)"$2>"#,
137+
options: [.regularExpression, .caseInsensitive]
138+
)
139+
140+
return modified
141+
}
142+
143+
public var body: some View {
144+
if let url = svgURL {
145+
if let modifiedContent = modifiedSVGContent {
146+
SVGView(string: modifiedContent)
147+
.frame(width: size, height: size)
148+
} else {
149+
SVGView(contentsOf: url)
150+
.frame(width: size, height: size)
151+
}
152+
} else {
153+
Rectangle()
154+
.stroke(color, lineWidth: strokeWidth)
155+
.frame(width: size, height: size)
156+
}
157+
}
158+
}

0 commit comments

Comments
 (0)