A collection of many useful Swift and SwiftUI extensions and components

PhantomKit

PhantomKit is a collection of many useful Swift and SwiftUI extensions and components. It is an invisible layer that supports your application and makes writing Swift code easier.

Requirements

  • iOS 14.0+
  • Xcode 12.0+
  • Swift 5.3+

Installation

Swift Package Manager

PhantomKit is available as a Swift Package.

Alternatively, you can add PhantomKit as a SPM dependency:

.package(url: "https://github.com/pawello2222/PhantomKit.git", .upToNextMajor(from: "0.0.1"))

Tip: to make PhantomKit truly invisible add the below line in the main file:

@_exported import PhantomKit

Highlights

Opening a NavigationLink on tap gesture

  • Natively
@State private var isActive = false
NavigationLink("", destination: Text("Destination"), isActive: $isActive)
Text("Go to...")
    .onTapGesture {
        isActive = true
    }
  • With PhantomKit
Text("Go to...")
    .link(triggeredBy: .tap, destination: Text("Destination"))
Text("Go to...")
    .presentation(method: .link, onTrigger: {}, onDismiss: {}) {
        Text("Destination")
    }

Opening a Web View

extension WebEndpoint {
    static var feedback: Self {
        .init(
            title: "Feedback",
            url: URL(string: "<feedback_url>")
        )
    }
}
Text("Share feedback")
    .webView(endpoint: .feedback)

Text("Share feedback")
    .webView(triggeredBy: .plainButton endpoint: .feedback)
    
Text("Share feedback")
    .webView(openedAs: .fullScreen, endpoint: .feedback, edgesIgnoringSafeArea: .all) {
        print("onDismiss")
    }

SwiftUI extensions

  • aligned
public func aligned(_ alignemnt: HorizontalAlignment) -> some View

public func aligned(_ alignemnt: VerticalAlignment) -> some View

Before

HStack {
    Spacer()
    Text("Hello World")
}

After

Text("Hello World")
    .aligned(.trailing)
  • expandingBackgroundColor
public func expandingBackgroundColor(_ color: Color, edgesIgnoringSafeArea: Edge.Set = .all) -> some View

Before

ZStack {
    Color.red
        .edgesIgnoringSafeArea(edgesIgnoringSafeArea)
    Text("Hello World")
}

After

Text("Hello World")
    .expandingBackgroundColor(.red)

Xcore extensions

  • Date manipulation

Before

let nextMonth = Calendar.current.date(byAdding: .month, value: 1, to: Date())!
let isSameMonth = Calendar.current.compare(nextMonth, to: Date(), toGranularity: .month) == .orderedSame

After

let nextMonth = Date().adjusting(.month, by: 1)
let isSameMonth = Date().isSame(nextMonth, granularity: .month)
  • In-flight modifications with Appliable and MutableAppliable

Before

static let numberFormatter: NumberFormatter = {
    let formatter = NumberFormatter()
    formatter.locale = .init(identifier: "en_US_POSIX")
    formatter.maximumFractionDigits = 3
    return formatter
}()

After

static let numberFormatter = NumberFormatter().apply {
    $0.locale = .usPosix
    $0.maximumFractionDigits = 3
}

and many more...!

Reference

Routing

Two enums for native SwiftUI routing:

extension PresentationMethod {
    public enum Transition {
        case link
        case sheet
        case fullScreen
    }
}
extension PresentationMethod {
    public enum Trigger {
        case tap
        case button(style: AnyButtonStyle)
        case primitiveButton(style: AnyPrimitiveButtonStyle)
    }
}

View helpers:

extension View {
    public func presentation<Content>(
        method: PresentationMethod,
        onTrigger: (() -> Void)? = nil,
        onDismiss: (() -> Void)? = nil,
        @ViewBuilder content: @escaping () -> Content
    ) -> some View where Content: View

Examples:

Text("Go to...")
    .presentation(method: .link, onTrigger: {}, onDismiss: {}) {
        Text("Destination")
    }

Text("Go to...")
    .link(triggeredBy: .tap, destination: Text("Destination"))

Text("Go to...")
    .sheet(triggeredBy: .plainButton, destination: Text("Sheet"))

Text("Go to...")
    .fullScreen(triggeredBy: .styledButton(BorderlessButtonStyle()), destination: Text("Full Screen"))

Text("Go to...")
    .actionSheet(triggeredBy: .fillButton) {
        ActionSheet(...)
    }

Formatting

LocalizedFormatter

A custom LocalizedFormatter that automatically formats numbers, decimals and currencies for a specified locale:

extension LocalizedFormatter {
    public static var currency = makeCurrencyFormatter(locale: .current, currencyCode: "USD")
    public static var decimal = makeDecimalFormatter(locale: .current)
    public static var percent = makePercentFormatter(locale: .current)
}

Examples:

let formatter = LocalizedFormatter.makeDecimalFormatter(locale: .init(identifier: "en_US"))

expect(formatter.string(from: 48_729_432)).to(equal("48,729,432"))
expect(formatter.string(from: 48_729_432, abbreviation: .capitalized)).to(equal("48.73M"))
expect(formatter.string(from: 123.456, sign: .both)).to(equal("+123.46"))
expect(formatter.string(from: 0.123456789, precision: .default)).to(equal("0.12"))
expect(formatter.string(from: 0.123456789, precision: .constant(4))).to(equal("0.1235"))
let formatter = LocalizedFormatter.makeCurrencyFormatter(locale: .init(identifier: "en_US"), currencyCode: "USD")

expect(formatter.string(from: 432)).to(equal("$432.00"))
expect(formatter.string(from: 1432.99, abbreviation: .default)).to(equal("$1.43k"))
expect(formatter.string(from: 123.456, sign: .both)).to(equal("+$123.46"))

LocalizedDateFormatter

A custom LocalizedDateFormatter that automatically formats date and time for a specified locale:

extension LocalizedDateFormatter {
    public static var date = makeDateFormatter(localizedFormat: "yyyyMMdd")
    public static var datetime = makeDateFormatter(localizedFormat: "yyyyMMddjjmmss")
}

Examples:

let formatter = LocalizedDateFormatter.makeDateFormatter(locale: .init(identifier: "pl_PL"))

let date = Date(year: 2000, month: 3, day: 24)

expect(formatter.string(from: date)).to(equal("24.03.2000"))
let formatter = LocalizedDateFormatter.makeDateFormatter(
    locale: .init(identifier: "en_US"),
    localizedFormat: "yyyyMMddjjmmss"
)

let date = Date(year: 2000, month: 3, day: 24, hour: 16, minute: 14, second: 44)

expect(formatter.string(from: date)).to(equal("03/24/2000, 4:14:44 PM"))

Roadmap

  • [x] SwiftUI
  • [x] Routing
  • [x] Localized formatters
  • [x] Network layer
  • [x] Third party extensions
  • [x] SPM compatibility
  • [ ] Database extensions
    • [ ] Core Data
    • [ ] Realm
  • [ ] Complete documentation

GitHub

https://github.com/pawello2222/PhantomKit