5 Commits

Author SHA1 Message Date
1d5067212a Merge pull request '[fix] LCENavigationView: remove phantom glass button, center title safely' (#11) from fix/navigation/glass-duplicate-button into main
Reviewed-on: #11
2026-09-01 16:26:03 -03:00
Daniel Arantes Loverde
72a5b59c44 [fix] LCENavigationView: remove phantom glass button, center title safely
Dummy mirrored button used to balance title centering rendered as a
visible empty glass pill under iOS 26's automatic button chrome. Side
buttons now only render when actually configured, title centers via
ZStack independent of button widths, and title padding is derived from
measured button width so long titles truncate instead of overlapping.
Glass button style is opt-in via setGlassButtonsEnabled(_:), off by
default.
2026-09-01 16:20:19 -03:00
Daniel Arantes Loverde
d678965154 [docs] Trim README to Documentation links; fix signature image 2026-08-30 10:02:19 -03:00
Daniel Arantes Loverde
0cceda1ad9 [docs] Trim README to intro + install + Documentation links
Remove the legacy inline usage snippets (background thread, nav completion
handler, networking example, 'Another components'). Replace the dead
signature GIF with the public GitHub mark linking to the resume repo.
2026-08-30 10:02:18 -03:00
Daniel Arantes Loverde
2cd52d3d12 [docs] Split reference docs into Extensions / SwiftUI / UIKit guides
Full per-symbol reference for every public extension, SwiftUI component, and
UIKit-era helper, as collapsible sections. README gets a Documentation table
linking all four guides; footer trimmed to the resume link. Docs only - no
code changes.
2026-08-29 21:27:49 -03:00
2 changed files with 113 additions and 203 deletions

109
README.md
View File

@@ -1,94 +1,43 @@
![](loverde_company_logo_full.png)
![](loverde_company_logo_full.png)
Loverde Co. Essentials Swift Scripts
----
# Loverde Co. Essentials
This is a repository of essential scripts written in Swift for Loverde Co. used to save time on re-writing and keeping it on all other projects. So this Cocoapods will evolve with Swift and will improve with every release!
Essential Swift scripts, extensions, SwiftUI components, and UIKit-era helpers,
shared across Loverde Co. projects. Evolves with Swift, improves every release.
## Requirements
- iOS 15.* or newer, Swift 5.* or newer.
## Features
- [x] Many usefull scripts extensions
- [x] `API` — typed async networking + multipart uploads
- [x] SwiftUI components (`LCENavigationView`) and UIKit-era helpers
- iOS 15 or newer · Swift 5 or newer
## Installation — Swift Package Manager
```swift
dependencies: [
.package(url: "https://git.loverde.com.br/Loverde-Company-LTDA/LCEssentials", .upToNextMajor(from: "2.0.0"))
]
```
Or add it in Xcode via **File ▸ Add Package Dependencies…** with the URL:
```
https://git.loverde.com.br/Loverde-Company-LTDA/LCEssentials
```
```swift
import LCEssentials
```
## Documentation
| Guide | Covers |
| --- | --- |
| **[API.md](Documentation/API.md)** | `API` networking — requests, multipart uploads, client certificates, error handling, and why it beats a hand-rolled `URLSession` |
| **[API.md](Documentation/API.md)** | `API` networking — typed requests, multipart uploads, client certificates, error handling, and why it beats a hand-rolled `URLSession` |
| **[Extensions.md](Documentation/Extensions.md)** | Foundation / value-type / string / collection / numeric / date / crypto extensions and the `LCEssentials` namespace |
| **[SwiftUI.md](Documentation/SwiftUI.md)** | SwiftUI components and `View` helpers |
| **[UIKit.md](Documentation/UIKit.md)** | Programmatic layout & constraints, view/control extensions, navigation, tables, and drop-in components |
| **[SwiftUI.md](Documentation/SwiftUI.md)** | SwiftUI components (`LCENavigationView`) and `View` helpers |
| **[UIKit.md](Documentation/UIKit.md)** | Programmatic layout & constraints, view/control extensions, navigation, tables, and drop-in components (`LCSnackBarView`, image picker/zoom, GIF loading) |
---
Installation
----
#### Swift Package Manager (SPM)
``` swift
dependencies: [
.package(url: "https://git.loverde.com.br/Loverde-Company-LTDA/LCEssentials", .upToNextMajor(from: "1.0.0"))
]
```
Daniel Arantes Loverde — <daniel@loverde.com.br>
You can also add it via XCode SPM editor with URL:
``` swift
https://git.loverde.com.br/Loverde-Company-LTDA/LCEssentials
```
## Usage example
* Background Trhead
```swift
LCEssentials.backgroundThread(delay: 0.6, background: {
//Do something im background
}) {
//When finish, update UI
}
```
* NavigationController with Completion Handler
```swift
self.navigationController?.popViewControllerWithHandler(completion: {
//Do some stuff after pop
})
//or more simple
self.navigationController?.popViewControllerWithHandler {
//Do some stuff after pop
}
```
* Networking with `API`
```swift
struct User: Decodable, Sendable { let id: Int; let name: String }
let user: User = try await API.shared.request(
url: "https://api.example.com/users/{id}",
method: .get,
pathParams: ["id": "42"]
)
```
Full guide — requests, uploads, client certificates, error handling, and why it
beats a hand-rolled `URLSession`: **[Documentation/API.md](Documentation/API.md)**
## Another components
> LCESnackBarView - **great way to send feedback to user**
And then import `LCEssentials ` wherever you import UIKit or SwiftUI
``` swift
import LCEssentials
```
Any question or doubts, please send thru email
Daniel Arantes Loverde - <daniel@loverde.com.br>
[![Alt text](https://loverde.com.br/_signature/loverde_github_mail.gif "My Resume")](https://github.com/loverde-co/resume/)
Autor: Daniel Arantes Loverde
[<img src="https://github.githubassets.com/images/modules/logos_page/GitHub-Mark.png" width="28" alt="GitHub">](https://github.com/loverde-co/resume/)

View File

@@ -42,13 +42,46 @@ class LCENavigationState: ObservableObject {
/// A boolean value that controls the visibility of the navigation bar.
@Published var hideNavigationBar: Bool = false
/// The title view of the navigation bar.
@Published var title: (any View) = Text("")
/// The subtitle view of the navigation bar.
@Published var subTitle: (any View) = Text("")
/// The background color of the navigation bar.
@Published var navigationBarBackgroundColor: Color = .clear
/// Whether a left button was actually configured via `setLeftButton`.
@Published var hasLeftButton: Bool = false
/// Whether a right button was actually configured via `setRightButton`.
@Published var hasRightButton: Bool = false
/// Whether buttons should opt into the system Liquid Glass button style (iOS 26+). Off by default.
@Published var useGlassButtons: Bool = false
}
/// `PreferenceKey` used to measure the widest side button so the title can be padded symmetrically and stay centered without overlapping either button.
@available(iOS 15, *)
private struct LCENavButtonWidthPreferenceKey: PreferenceKey {
static var defaultValue: CGFloat { 0 }
static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
value = max(value, nextValue())
}
}
@available(iOS 15, *)
private extension View {
/// Applies `.glass` button style on iOS 26+ when enabled, otherwise falls back to `.plain` the navigation bar is not designed around glass, but stays opt-in ready.
@ViewBuilder
func lce_applyGlassIfEnabled(_ enabled: Bool) -> some View {
if enabled {
if #available(iOS 26.0, *) {
self.buttonStyle(.glass)
} else {
self.buttonStyle(.plain)
}
} else {
self.buttonStyle(.plain)
}
}
}
/// `LCENavigationView` is a SwiftUI `View` that provides a customizable navigation bar.
@@ -60,7 +93,10 @@ public struct LCENavigationView<Content: View>: View {
/// The content view displayed below the navigation bar.
let content: Content
/// The measured width of the widest side button, used to pad the title so it never overlaps either button.
@State private var maxButtonWidth: CGFloat = 0
/// Initializes a new `LCENavigationView` instance.
/// - Parameters:
/// - title: The title view for the navigation bar. Defaults to an empty `Text`.
@@ -92,20 +128,31 @@ public struct LCENavigationView<Content: View>: View {
/// The private `NavigationBarView` that lays out the navigation bar components.
private var NavigationBarView: some View {
HStack {
NavLeftButton
Spacer()
ZStack {
TitleView
Spacer()
NavRightButton
.padding(.horizontal, maxButtonWidth)
.lineLimit(1)
.minimumScaleFactor(0.7)
.frame(maxWidth: .infinity)
HStack {
if state.hasLeftButton {
NavLeftButton
}
Spacer()
if state.hasRightButton {
NavRightButton
}
}
}
.font(.headline)
.padding()
.background {
state.navigationBarBackgroundColor.ignoresSafeArea(edges: .top)
}
.onPreferenceChange(LCENavButtonWidthPreferenceKey.self) { maxButtonWidth = $0 }
}
/// The private `TitleView` that displays the title and subtitle.
private var TitleView: some View {
VStack {
@@ -115,8 +162,8 @@ public struct LCENavigationView<Content: View>: View {
}
}
}
/// The private `NavLeftButton` view.
/// The private `NavLeftButton` view. Only rendered when `setLeftButton` was actually called never a hidden placeholder.
private var NavLeftButton: some View {
Button(action: state.leftButtonAction) {
HStack {
@@ -126,9 +173,15 @@ public struct LCENavigationView<Content: View>: View {
state.leftButtonText
}
}
.lce_applyGlassIfEnabled(state.useGlassButtons)
.background(
GeometryReader { proxy in
Color.clear.preference(key: LCENavButtonWidthPreferenceKey.self, value: proxy.size.width)
}
)
}
/// The private `NavRightButton` view.
/// The private `NavRightButton` view. Only rendered when `setRightButton` was actually called never a hidden placeholder.
private var NavRightButton: some View {
Button(action: state.rightButtonAction) {
HStack {
@@ -138,6 +191,12 @@ public struct LCENavigationView<Content: View>: View {
}
}
}
.lce_applyGlassIfEnabled(state.useGlassButtons)
.background(
GeometryReader { proxy in
Color.clear.preference(key: LCENavButtonWidthPreferenceKey.self, value: proxy.size.width)
}
)
}
/// Sets the configuration for the right button of the navigation bar.
@@ -158,14 +217,8 @@ public struct LCENavigationView<Content: View>: View {
}
state.rightButtonText = text
state.rightButtonAction = action
if let string = state.leftButtonText.string, string.isEmpty {
state.leftButtonText = text.foregroundColor(.clear)
}
if state.leftButtonImage == nil {
state.leftButtonImage = image?.foregroundColor(.clear) as? AnyView
}
state.hasRightButton = true
return self
}
@@ -187,14 +240,17 @@ public struct LCENavigationView<Content: View>: View {
}
state.leftButtonText = text
state.leftButtonAction = action
if let string = state.rightButtonText.string, string.isEmpty {
state.rightButtonText = text.foregroundColor(.clear)
}
if state.rightButtonImage == nil {
state.rightButtonImage = image?.foregroundColor(.clear) as? AnyView
}
state.hasLeftButton = true
return self
}
/// Opts the navigation bar's buttons into the system Liquid Glass `.glass` button style on iOS 26+.
/// The navigation bar is designed as a plain, non-glass component by default; call this to enable glass explicitly.
/// - Parameter enabled: Whether buttons should use the glass style.
/// - Returns: The `LCENavigationView` instance for chaining.
public func setGlassButtonsEnabled(_ enabled: Bool) -> LCENavigationView {
state.useGlassButtons = enabled
return self
}
@@ -229,101 +285,6 @@ public struct LCENavigationView<Content: View>: View {
}
}
/// Extension to `FormatStyle` to format any value as a string.
@available(iOS 15.0, *)
extension FormatStyle {
/// Formats an input value if it matches the `FormatInput` type.
/// - Parameter value: The value to format as `Any`.
/// - Returns: The formatted output, or `nil` if the value type does not match.
func format(any value: Any) -> FormatOutput? {
if let v = value as? FormatInput {
return format(v)
}
return nil
}
}
/// Extension to `LocalizedStringKey` to resolve localized strings.
@available(iOS 15.0, *)
extension LocalizedStringKey {
/// Resolves the localized string key into a `String`.
/// - Returns: The resolved string, or `nil` if resolution fails.
var resolved: String? {
let mirror = Mirror(reflecting: self)
guard let key = mirror.descendant("key") as? String else {
return nil
}
guard let args = mirror.descendant("arguments") as? [Any] else {
return nil
}
let values = args.map { arg -> Any? in
let mirror = Mirror(reflecting: arg)
if let value = mirror.descendant("storage", "value", ".0") {
return value
}
guard let format = mirror.descendant("storage", "formatStyleValue", "format") as? any FormatStyle,
let input = mirror.descendant("storage", "formatStyleValue", "input") else {
return nil
}
return format.format(any: input)
}
let va = values.compactMap { arg -> CVarArg? in
switch arg {
case let i as Int: return i
case let i as Int64: return i
case let i as Int8: return i
case let i as Int16: return i
case let i as Int32: return i
case let u as UInt: return u
case let u as UInt64: return u
case let u as UInt8: return u
case let u as UInt16: return u
case let u as UInt32: return u
case let f as Float: return f
case let f as CGFloat: return f
case let d as Double: return d
case let o as NSObject: return o
default: return nil
}
}
if va.count != values.count {
return nil
}
return String.localizedStringWithFormat(key, va)
}
}
/// Extension to `Text` to retrieve its string content.
@available(iOS 15.0, *)
extension Text {
/// Returns the string representation of the `Text` view.
/// - Returns: The string content, or `nil` if it cannot be extracted.
var string: String? {
let mirror = Mirror(reflecting: self)
if let s = mirror.descendant("storage", "verbatim") as? String {
return s
} else if let attrStr = mirror.descendant("storage", "anyTextStorage", "str") as? AttributedString {
return String(attrStr.characters)
} else if let key = mirror.descendant("storage", "anyTextStorage", "key") as? LocalizedStringKey {
return key.resolved
} else if let format = mirror.descendant("storage", "anyTextStorage", "storage", "format") as? any FormatStyle,
let input = mirror.descendant("storage", "anyTextStorage", "storage", "input") {
return format.format(any: input) as? String
} else if let formatter = mirror.descendant("storage", "anyTextStorage", "formatter") as? Formatter,
let object = mirror.descendant("storage", "anyTextStorage", "object") {
return formatter.string(for: object)
}
return nil
}
}
//@available(iOS 15.0, *)
//struct LCENavigationView_Previews: PreviewProvider {
// static var previews: some View {