Skip to content

Instantly share code, notes, and snippets.

@mbrandonw
Last active January 24, 2023 20:22
Show Gist options
  • Save mbrandonw/ed5d14b86e263fa6df008329cba74142 to your computer and use it in GitHub Desktop.
Save mbrandonw/ed5d14b86e263fa6df008329cba74142 to your computer and use it in GitHub Desktop.
Reverse engineering SwiftUI's NavigationPath
// ==============================================================================================|
// | |
// | http://pointfree.co/blog/posts/78-reverse-engineering-swiftui-s-navigationpath-codability |
// | |
// ==============================================================================================|
import Foundation
public struct NavPath {
var elements: [Any]
public init(_ elements: [Any] = []) {
self.elements = elements
}
public mutating func append(_ newElement: Any) {
self.elements.append(newElement)
}
public var isEmpty: Bool {
self.elements.isEmpty
}
mutating public func removeLast(_ k: Int) {
self.elements.removeLast(k)
}
}
extension NavPath: Encodable {
public func encode(to encoder: Encoder) throws {
var container = encoder.unkeyedContainer()
for element in elements.reversed() {
try container.encode(_mangledTypeName(type(of: element)))
guard let element = element as? any Encodable
else {
throw EncodingError.invalidValue(
element,
.init(
codingPath: container.codingPath,
debugDescription: "\(type(of: element)) is not encodable."
)
)
}
#if swift(<5.7)
func open<A: Encodable>(_: A.Type) throws -> Data {
try JSONEncoder().encode(element as! A)
}
let string = try String(
decoding: _openExistential(type(of: element), do: open),
as: UTF8.self
)
try container.encode(string)
#else
let string = try String(decoding: JSONEncoder().encode(element), as: UTF8.self)
try container.encode(string)
#endif
}
}
}
extension NavPath: Decodable {
public init(from decoder: Decoder) throws {
var container = try decoder.unkeyedContainer()
self.elements = []
while !container.isAtEnd {
let typeName = try container.decode(String.self)
guard let type = _typeByName(typeName) as? any Decodable.Type
else {
throw DecodingError.dataCorruptedError(
in: container,
debugDescription: "\(typeName) is not decodable."
)
}
let encodedValue = try container.decode(String.self)
#if swift(<5.7)
func decode<A: Decodable>(_: A.Type) throws -> A {
try JSONDecoder().decode(A.self, from: Data(encodedValue.utf8))
}
let value = try _openExistential(type, do: decode)
#else
let value = try JSONDecoder().decode(type, from: Data(encodedValue.utf8))
#endif
self.elements.insert(value, at: 0)
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment