Skip to content

Commit

Permalink
4.16.0
Browse files Browse the repository at this point in the history
  • Loading branch information
dankinsoid committed Mar 16, 2024
1 parent 88d23c4 commit 173c72d
Show file tree
Hide file tree
Showing 10 changed files with 145 additions and 75 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ import PackageDescription
let package = Package(
name: "SomeProject",
dependencies: [
.package(url: "https://github.com/dankinsoid/VDFlow.git", from: "4.15.0")
.package(url: "https://github.com/dankinsoid/VDFlow.git", from: "4.16.0")
],
targets: [
.target(name: "SomeProject", dependencies: ["VDFlow"])
Expand Down
32 changes: 18 additions & 14 deletions Sources/VDFlow/MutateID.swift
Original file line number Diff line number Diff line change
@@ -1,26 +1,30 @@
import Foundation

public struct MutateID: Equatable, Hashable, Codable {
public struct MutateID: Comparable, Hashable, Codable, Sendable {

var value: UInt64
private var mutationDate: UInt64?

init() {
value = 0
init() {
}

public init(from decoder: Decoder) throws {
let date = try UInt64(from: decoder)
mutationDate = date == 0 ? nil : date
}

public init(from decoder: Decoder) throws {
value = try UInt64(from: decoder)
}

public func encode(to encoder: Encoder) throws {
try value.encode(to: encoder)
}
public func encode(to encoder: Encoder) throws {
try (mutationDate ?? 0).encode(to: encoder)
}

mutating func update() {
value = DispatchTime.now().uptimeNanoseconds
public mutating func _update() {
mutationDate = DispatchTime.now().uptimeNanoseconds
}

public static func < (lhs: MutateID, rhs: MutateID) -> Bool {
lhs.value < rhs.value
(lhs.mutationDate ?? 0) < (rhs.mutationDate ?? 0)
}

var optional: MutateID? {
mutationDate.map { _ in self }
}
}
12 changes: 0 additions & 12 deletions Sources/VDFlow/NavigationSteps.swift
Original file line number Diff line number Diff line change
Expand Up @@ -247,18 +247,6 @@ public struct PopAction {
}
}

private extension UIViewController {

var stackID: IDWrapper? {
get { objc_getAssociatedObject(self, &stackIDKey) as? IDWrapper }
set { objc_setAssociatedObject(self, &stackIDKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) }
}
var stackTag: IDWrapper? {
get { objc_getAssociatedObject(self, &stackTagKey) as? IDWrapper }
set { objc_setAssociatedObject(self, &stackTagKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) }
}
}

private var stackIDKey = 0
private var stackTagKey = 0

Expand Down
6 changes: 3 additions & 3 deletions Sources/VDFlow/NavigationView+Ext.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ public extension NavigationLink {
step: StepBinding<T, D>,
@ViewBuilder destination: () -> Dest,
@ViewBuilder label: () -> Label
) where Destination == NavigationStepDestination<Dest, D>, T.Steps: OptionalStep, T: StepsCollection {
) where Destination == NavigationStepDestination<Dest, D>, T.AllSteps: ExpressibleByNilLiteral, T: StepsCollection {
self.init(isActive: step.isSelected) {
NavigationStepDestination(content: destination(), stepBinding: step.binding)
} label: {
Expand All @@ -33,7 +33,7 @@ public extension View {
_ root: Binding<Root>,
for step: WritableKeyPath<Root, StepWrapper<Root, Value>>,
@ViewBuilder destination: @escaping () -> some View
) -> some View where Root.Steps: OptionalStep {
) -> some View where Root.AllSteps: ExpressibleByNilLiteral {
navigationDestination(
step: StepBinding(root: root, keyPath: step),
destination: destination
Expand All @@ -43,7 +43,7 @@ public extension View {
func navigationDestination<Root: StepsCollection, Value>(
step: StepBinding<Root, Value>,
@ViewBuilder destination: @escaping () -> some View
) -> some View where Root.Steps: OptionalStep {
) -> some View where Root.AllSteps: ExpressibleByNilLiteral {
navigationDestination(isPresented: step.isSelected) {
destination()
.stepEnvironment(step.binding)
Expand Down
6 changes: 3 additions & 3 deletions Sources/VDFlow/StateStep.swift
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ public extension View {
.transformEnvironment(\.unselectStep) {
$0.insert(
{
if let none = (Root.Steps.self as? OptionalStep.Type)?.none as? Root.Steps {
if let none = (Root.AllSteps.self as? ExpressibleByNilLiteral.Type)?.init(nilLiteral: ()) as? Root.AllSteps {
binding.wrappedValue.selected = none
}
},
Expand All @@ -119,9 +119,9 @@ public extension View {
}
}

public extension Binding where Value: StepsCollection ,Value.Steps: OptionalStep {
public extension Binding where Value: StepsCollection, Value.AllSteps: ExpressibleByNilLiteral {

func isSelected(_ step: Value.Steps) -> Binding<Bool> {
func isSelected(_ step: Value.AllSteps) -> Binding<Bool> {
Binding<Bool> {
wrappedValue.selected == step
} set: {
Expand Down
6 changes: 3 additions & 3 deletions Sources/VDFlow/StepBinding.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import SwiftUI
public struct StepBinding<Root: StepsCollection, Value> {

@Binding var root: Root
public var step: Root.Steps {
public var step: Root.AllSteps {
root[keyPath: keyPath.appending(path: \.id)]
}

Expand Down Expand Up @@ -36,12 +36,12 @@ public struct StepBinding<Root: StepsCollection, Value> {
}
}

extension StepBinding where Root.Steps: OptionalStep {
extension StepBinding where Root.AllSteps: ExpressibleByNilLiteral {

public var isSelected: Binding<Bool> {
$root.isSelected(step)
}

public func isSelected(_ value: Value) -> Binding<Bool> {
Binding {
$root.isSelected(step).wrappedValue
Expand Down
22 changes: 18 additions & 4 deletions Sources/VDFlow/StepWrapper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ public extension StepsCollection {
@propertyWrapper
public struct StepWrapper<Parent: StepsCollection, Value>: Identifiable {

public let id: Parent.Steps
public let id: Parent.AllSteps
public var _mutateID = MutateID()
public var wrappedValue: Value
public var projectedValue: StepWrapper {
get { self }
Expand All @@ -17,23 +18,36 @@ public struct StepWrapper<Parent: StepsCollection, Value>: Identifiable {

public init(
wrappedValue: Value,
_ id: Parent.Steps
_ id: Parent.AllSteps
) {
self.wrappedValue = wrappedValue
self.id = id
}

public mutating func select() {
_mutateID._update()
}

public mutating func select(with value: Value) {
wrappedValue = value
select()
}

public var _lastMutateID: (Parent.AllSteps, MutateID)? {
((wrappedValue as? any StepsCollection)?._lastMutateID?.optional ?? _mutateID.optional).map { (id, $0) }
}
}

public extension StepWrapper where Value == EmptyStep {

init(_ id: Parent.Steps) {
init(_ id: Parent.AllSteps) {
self.init(wrappedValue: EmptyStep(), id)
}
}

public extension StepWrapper {

init<T>(_ id: Parent.Steps) where T? == Value {
init<T>(_ id: Parent.AllSteps) where T? == Value {
self.init(wrappedValue: nil, id)
}
}
Expand Down
42 changes: 28 additions & 14 deletions Sources/VDFlow/StepsCollection.swift
Original file line number Diff line number Diff line change
@@ -1,19 +1,33 @@
import Foundation

public protocol StepsCollection where Steps.RawValue == String {
public protocol StepsCollection {

associatedtype Steps: RawRepresentable & CaseIterable & Hashable & Codable & Sendable
var selected: Steps { get set }
associatedtype AllSteps: Hashable & Codable & Sendable
var selected: AllSteps { get set }
static var _mutateIDs: [AllSteps: WritableKeyPath<Self, MutateID>] { get }
var _lastMutateID: MutateID? { get }
}

public protocol OptionalStep: ExpressibleByNilLiteral {

static var none: Self { get }
}

extension OptionalStep {

public init(nilLiteral: ()) {
self = .none
}
}
//extension Optional: CaseIterable where Wrapped: CaseIterable {
//
// public static var allCases: [Wrapped?] {
// [.none] + Wrapped.allCases.map { $0 }
// }
//}
//
//extension Optional: RawRepresentable where Wrapped: RawRepresentable {
//
// public init?(rawValue: Wrapped.RawValue?) {
// switch rawValue {
// case let .some(rawValue):
// guard let wrapped = Wrapped(rawValue: rawValue) else { return nil }
// self = .some(wrapped)
// case .none:
// self = .none
// }
// }
//
// public var rawValue: Wrapped.RawValue? {
// self?.rawValue
// }
//}
74 changes: 62 additions & 12 deletions Sources/VDFlowMacros/StepsMacro.swift
Original file line number Diff line number Diff line change
Expand Up @@ -68,14 +68,14 @@ public struct StepsMacro: MemberAttributeMacro, ExtensionMacro, MemberMacro, Acc
]
}

public static func expansion(
public static func enumExpansion(
of node: AttributeSyntax,
providingMembersOf declaration: some DeclGroupSyntax,
in context: some MacroExpansionContext
) throws -> [DeclSyntax] {
guard declaration.is(EnumDeclSyntax.self) else {
if declaration.is(StructDeclSyntax.self) {
return try structExpansion(of: node, providingMembersOf: declaration, in: context)
return try expansion(of: node, providingMembersOf: declaration, in: context)
}
throw MacroError("Steps macro can only be applied to enums or structs")
}
Expand All @@ -84,9 +84,9 @@ public struct StepsMacro: MemberAttributeMacro, ExtensionMacro, MemberMacro, Acc
}
guard cases.contains(where: \.hasParameters) else {
return ["""
public typealias Steps = Self
public typealias AllSteps = Self
public var selected: Steps {
public var selected: Self {
get { self }
set { self = newValue }
}
Expand Down Expand Up @@ -198,11 +198,14 @@ public struct StepsMacro: MemberAttributeMacro, ExtensionMacro, MemberMacro, Acc
return result
}

public static func structExpansion(
public static func expansion(
of node: AttributeSyntax,
providingMembersOf declaration: some DeclGroupSyntax,
in context: some MacroExpansionContext
) throws -> [DeclSyntax] {
guard declaration.is(StructDeclSyntax.self) else {
throw MacroError("Steps macro can only be applied to structs")
}
var isOptional = false
var cases: [String] = []
var functions: [String: String] = [:]
Expand Down Expand Up @@ -233,8 +236,12 @@ public struct StepsMacro: MemberAttributeMacro, ExtensionMacro, MemberMacro, Acc
}
}
var defaultValue = binding.initializer?.value.description
if defaultValue == nil, type.isOptional {
defaultValue = "nil"
if defaultValue == nil {
if type.isOptional {
defaultValue = "nil"
} else if !type.isEmpty {
throw MacroError("Default value of `\(name)` must be provided")
}
}
if functions[name] == nil {
functions[name] = "(_ value: \(type)\(defaultValue.map { " = \($0)" } ?? ""))"
Expand All @@ -245,23 +252,66 @@ public struct StepsMacro: MemberAttributeMacro, ExtensionMacro, MemberMacro, Acc
isOptional = true
}
}

guard !cases.isEmpty, cases != ["none"] else {
throw MacroError("Steps must have at least one variable")
}

let stepsType = "Steps" + (isOptional ? "?" : "")
var result: [DeclSyntax] = []
let initialSelected: DeclSyntax =
"""
private let initialSelected: \(raw: stepsType)
"""
result.append(initialSelected)
let initDecl: DeclSyntax =
"""
private init(_ selected: Steps\(raw: isOptional ? " = nil" : "")) {
self.selected = selected
private init(_ selected: \(raw: stepsType)) {
initialSelected = selected
}
"""
let _lastMutateStepID: DeclSyntax =
"""
private var lastMutateStepID: (\(raw: stepsType), MutateID)? {
[
\(raw: cases.map { "_\($0)._lastMutateID" }.joined(separator: ",\n"))
]
.compactMap { $0 }
.sorted(by: { $0.1 > $1.1 })
.first
}
"""
result.append(_lastMutateStepID)
let lastMutateID: DeclSyntax = "public var _lastMutateID: MutateID? { lastMutateStepID?.1 }"
result.append(lastMutateID)
result.append(initDecl)
result.append("public var selected: Steps")
let selected: DeclSyntax =
"""
public var selected: \(raw: stepsType) {
get { if let result = lastMutateStepID { return result.0 } else { return initialSelected } }
set {
guard let keyPath = Self._mutateIDs[newValue] else { return }
self[keyPath: keyPath]._update()
}
}
"""
result.append(selected)
let typealiasDecl: DeclSyntax = "public typealias AllSteps = \(raw: stepsType)"
result.append(typealiasDecl)
let stepsEnum: DeclSyntax =
"""
public enum Steps: String, CaseIterable, Codable, Sendable\(raw: isOptional ? ", OptionalStep" : "") {
case \(raw: cases.joined(separator: ", "))
public enum Steps: String, CaseIterable, Codable, Sendable, Hashable {
case \(raw: cases.filter({ $0 != "none" }).joined(separator: ", "))
}
"""
result.append(stepsEnum)
let mutateIDs: DeclSyntax =
"""
public static var _mutateIDs: [AllSteps: WritableKeyPath<Self, MutateID>] {
[\(raw: cases.map { ".\($0): \\.$\($0)._mutateID" }.joined(separator: ", "))]
}
"""
result.append(mutateIDs)
result += cases.map {
let function = functions[$0] ?? ""
let isVar = function.isEmpty
Expand Down
Loading

0 comments on commit 173c72d

Please sign in to comment.