Skip to content

Instantly share code, notes, and snippets.

View IanKeen's full-sized avatar
🏂

Ian Keen IanKeen

🏂
View GitHub Profile
@IanKeen
IanKeen / View+Discover.swift
Last active February 13, 2024 08:00
SwiftUI: discover underlying components to fill in gaps in SwiftUI api
import SwiftUI
extension View {
public func discover<T: UIView>(
tag: Int = .random(in: (.min)...(.max)),
where predicate: @escaping (T) -> Bool = { _ in true },
_ closure: @escaping (T) -> Void
) -> some View {
self.overlay(
DiscoveryView(tag: tag)
@IanKeen
IanKeen / LosslessKey.swift
Last active August 12, 2021 20:13
PropertyWrapper: Allow en/de-coding of String keys to non-String types
@propertyWrapper
struct LosslessKey<T: Hashable & LosslessStringConvertible, U> {
var wrappedValue: [T: U]
}
extension LosslessKey: Codable where T: Codable, U: Codable {
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
var result: [T: U] = [:]
for (key, value) in try container.decode([String: U].self) {
guard let key = T.init(key) else {
@IanKeen
IanKeen / CustomEquatable.swift
Created July 27, 2021 21:26
PropertyWrapper: Custom/Selective Equatable & Hashable conformance using PWs to choose which properties are used
public protocol CustomEquatable: Equatable { }
private extension CustomEquatable {
var equatables: [AnyEquatable] {
return Mirror(reflecting: self)
.children
.compactMap { ($0.value as? EquatingProperty)?.anyEquatable }
}
}
@IanKeen
IanKeen / View+Relative.swift
Last active January 16, 2023 13:03
SwiftUI relative frame
extension View {
func relative(width: CGFloat? = nil, height: CGFloat? = nil, alignment: Alignment = .center) -> some View {
Color.clear
.frame(maxWidth: width.map { _ in .infinity }, maxHeight: height.map { _ in .infinity })
.overlay(GeometryReader { proxy in
ZStack {
self.frame(
width: width.map { proxy.size.width * $0 },
height: height.map { proxy.size.height * $0 }
)
@IanKeen
IanKeen / ShareReplay.swift
Created June 3, 2021 19:38
Combine: ShareReplay - Shares the underlying resource and replays the last emitted value (if there was one) to new subscribers
extension Publisher {
func shareReplay() -> AnyPublisher<Output, Failure> {
let subject = CurrentValueSubject<Output?, Failure>(nil)
return map { $0 }
.multicast(subject: subject)
.autoconnect()
.compactMap { $0 }
.eraseToAnyPublisher()
}
@IanKeen
IanKeen / Sync.swift
Last active June 1, 2021 20:51
Sync helper to make an async func blocking, useful for test suites
func sync<T, U>(file: StaticString = #file, line: UInt = #line, timeOut: TimeInterval = 3, _ function: @escaping (@escaping (T) -> Void) -> U) -> T {
let group = DispatchGroup()
group.enter()
var result: T!
DispatchQueue.global().async {
_ = function {
result = $0
group.leave()
}
@IanKeen
IanKeen / Coding.swift
Created April 10, 2021 18:57
PropertyWrapper: Add Codable support for NSCoding types
import Foundation
@propertyWrapper
struct Coding<T: NSObject & NSSecureCoding>: Codable {
var wrappedValue: T
init(from decoder: Decoder) throws {
let string = try String(from: decoder)
guard let value = try NSKeyedUnarchiver.unarchivedObject(ofClass: T.self, from: Data(string.utf8)) else {
throw DecodingError.dataCorrupted(.init(codingPath: decoder.codingPath, debugDescription: ""))
@IanKeen
IanKeen / AnyPublisher+Extension.swift
Created March 17, 2021 17:02
Extension to create an AnyPublisher to easily 'lift' async code into Combine
extension AnyPublisher where Failure: Error {
struct Subscriber {
fileprivate let send: (Output) -> Void
fileprivate let complete: (Subscribers.Completion<Failure>) -> Void
func send(_ value: Output) { self.send(value) }
func send(completion: Subscribers.Completion<Failure>) { self.complete(completion) }
}
init(_ closure: (Subscriber) -> AnyCancellable) {
@IanKeen
IanKeen / Example.swift
Created February 7, 2021 03:09
PropertyWrapper: Ignore codable properties
struct Foo: Codable {
var name: String
@Ignore var foo: Int?
}
let model = Foo(name: "Ian", foo: 42)
let data = try! JSONEncoder().encode(model)
print(String(data: data, encoding: .utf8)!) // {"name":"Ian"}
@IanKeen
IanKeen / Banner.swift
Last active March 1, 2024 21:49
Persistent banner in SwiftUI : Supports top/bottom edge, swipe to dismiss + auto dismissal after time
public enum BannerEdge {
case top, bottom
}
public enum BannerAutoDismiss {
case after(TimeInterval)
case never
}
extension View {
public func banner<C: View>(