From c549e24cc4ee4eae72d01a26f1a39bd595895609 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=CC=8Cimon=20S=CC=8Cesta=CC=81k?= Date: Tue, 28 Jan 2025 11:36:44 +0700 Subject: [PATCH 1/6] Add SheetDetent --- .../Architecture/ModalCoverModel.swift | 26 +++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/Sources/FuturedArchitecture/Architecture/ModalCoverModel.swift b/Sources/FuturedArchitecture/Architecture/ModalCoverModel.swift index 21da67a..b3a61b5 100644 --- a/Sources/FuturedArchitecture/Architecture/ModalCoverModel.swift +++ b/Sources/FuturedArchitecture/Architecture/ModalCoverModel.swift @@ -1,13 +1,35 @@ // // ModalCoverModel.swift -// +// // // Created by Simon Sestak on 01/08/2024. // +import SwiftUI import Foundation -/// Style of the modally presented view. + +public enum SheetDetent: Hashable { + case medium + case large + case height + case fraction(CGFloat) + + func detent(size: CGSize) -> PresentationDetent { + switch self { + case .medium: + return .medium + case .large: + return .large + case .height: + return .height(size.height) + case let .fraction(fraction): + return .fraction(fraction) + } + } +} + +/// Style of the modally presented view. /// /// It is intended to be used with ``ModalCoverModel``. Style has been placed to /// the global scope, since the Model is generic. From 962d4c8eec8dc14e1d4f220e385a30f0bae4c025 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=CC=8Cimon=20S=CC=8Cesta=CC=81k?= Date: Tue, 28 Jan 2025 11:38:12 +0700 Subject: [PATCH 2/6] Add readSizeModifier --- .../Architecture/NavigationStackFlow.swift | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Sources/FuturedArchitecture/Architecture/NavigationStackFlow.swift b/Sources/FuturedArchitecture/Architecture/NavigationStackFlow.swift index a4bd92c..abd212b 100644 --- a/Sources/FuturedArchitecture/Architecture/NavigationStackFlow.swift +++ b/Sources/FuturedArchitecture/Architecture/NavigationStackFlow.swift @@ -50,3 +50,15 @@ public struct NavigationStackFlow Void) -> some View { + background( + GeometryReader { proxy in + Color.clear + .onAppear { action(proxy.size) } + .onChange(of: proxy.size) { action($0) } + } + ) + } +} From b650e195440f6c4919b93ed3fe7ecd906587ffe9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=CC=8Cimon=20S=CC=8Cesta=CC=81k?= Date: Sat, 2 Nov 2024 14:45:59 +0100 Subject: [PATCH 3/6] Add detents support in modal presentation of basic views --- .../Architecture/Coordinator.swift | 4 +-- .../Architecture/ModalCoverModel.swift | 24 ++++++++++++- .../Architecture/NavigationStackFlow.swift | 36 +++++++++++++------ .../Architecture/TabViewFlow.swift | 36 +++++++++++++------ 4 files changed, 75 insertions(+), 25 deletions(-) diff --git a/Sources/FuturedArchitecture/Architecture/Coordinator.swift b/Sources/FuturedArchitecture/Architecture/Coordinator.swift index 400797a..75f095e 100644 --- a/Sources/FuturedArchitecture/Architecture/Coordinator.swift +++ b/Sources/FuturedArchitecture/Architecture/Coordinator.swift @@ -49,9 +49,9 @@ public extension Coordinator { /// - type: Kind of modal presentation. func present(modal destination: Destination, type: ModalCoverModelStyle) { switch type { - case .sheet: + case let .sheet(detents): Task { @MainActor in - self.modalCover = .init(destination: destination, style: .sheet) + self.modalCover = .init(destination: destination, style: .sheet(detents: detents)) } #if !os(macOS) case .fullscreenCover: diff --git a/Sources/FuturedArchitecture/Architecture/ModalCoverModel.swift b/Sources/FuturedArchitecture/Architecture/ModalCoverModel.swift index b3a61b5..af1e26c 100644 --- a/Sources/FuturedArchitecture/Architecture/ModalCoverModel.swift +++ b/Sources/FuturedArchitecture/Architecture/ModalCoverModel.swift @@ -34,10 +34,32 @@ public enum SheetDetent: Hashable { /// It is intended to be used with ``ModalCoverModel``. Style has been placed to /// the global scope, since the Model is generic. public enum ModalCoverModelStyle { - case sheet + case sheet(detents: Set? = nil) #if !os(macOS) case fullscreenCover #endif + + + func detents(size: CGSize) -> Set? { + if case let .sheet(detents) = self, let detents { + return Set(detents.map { $0.detent(size: size) }) + } + return nil + } + + var isSheet: Bool { + if case .sheet = self { + return true + } + return false + } + + var hasDetents: Bool { + if case let .sheet(detents) = self { + return detents != nil + } + return false + } } /// This struct is a model associating presentation style with a destination on a specific ``Coordinator``. diff --git a/Sources/FuturedArchitecture/Architecture/NavigationStackFlow.swift b/Sources/FuturedArchitecture/Architecture/NavigationStackFlow.swift index abd212b..2063dbf 100644 --- a/Sources/FuturedArchitecture/Architecture/NavigationStackFlow.swift +++ b/Sources/FuturedArchitecture/Architecture/NavigationStackFlow.swift @@ -6,6 +6,9 @@ public struct NavigationStackFlow Content + /// Use in case when modal views presented by this coordinator should have detents. + @State private var modalDetents: Set? + /// - Parameters: /// - coordinator: The instance of the coordinator used as the model and retained as the ``SwiftUI.StateObject`` /// - content: The root view of this navigation stack. The ``navigationDestination(for:destination:)`` modifier @@ -20,32 +23,43 @@ public struct NavigationStackFlow { + @ViewBuilder + private func modalScene(for model: ModalCoverModel) -> some View { + if model.style.hasDetents { + coordinator.scene(for: model.destination) + .readSize { modalDetents = model.style.detents(size: $0) } + .presentationDetents(modalDetents ?? []) + } else { + coordinator.scene(for: model.destination) + } + } + + private var sheetBinding: Binding?> { .init { - coordinator.modalCover?.style == .sheet ? coordinator.modalCover?.destination : nil - } set: { destination in - coordinator.modalCover = destination.map { .init(destination: $0, style: .sheet) } + coordinator.modalCover?.style.isSheet == true ? coordinator.modalCover : nil + } set: { _ in + coordinator.modalCover = nil } } #if !os(macOS) - private var fullscreenCoverBinding: Binding { + private var fullscreenCoverBinding: Binding?> { .init { - coordinator.modalCover?.style == .fullscreenCover ? coordinator.modalCover?.destination : nil - } set: { destination in - coordinator.modalCover = destination.map { .init(destination: $0, style: .fullscreenCover) } + coordinator.modalCover?.style.isSheet == false ? coordinator.modalCover : nil + } set: { _ in + coordinator.modalCover = nil } } #endif diff --git a/Sources/FuturedArchitecture/Architecture/TabViewFlow.swift b/Sources/FuturedArchitecture/Architecture/TabViewFlow.swift index b16c8c1..47fb7ac 100644 --- a/Sources/FuturedArchitecture/Architecture/TabViewFlow.swift +++ b/Sources/FuturedArchitecture/Architecture/TabViewFlow.swift @@ -7,6 +7,9 @@ public struct TabViewFlow: View { @StateObject private var coordinator: Coordinator @ViewBuilder private let content: () -> Content + /// Use in case when modal views presented by this coordinator should have detents. + @State private var modalDetents: Set? + /// - Parameters: /// - coordinator: The instance of the coordinator used as the model and retained as the ``SwiftUI.StateObject`` /// - content: The definition of tabs held by this TabView should be placed into this ViewBuilder. You are required to use instances of `Tab` @@ -21,32 +24,43 @@ public struct TabViewFlow: View { TabView(selection: $coordinator.selectedTab) { content() } - .sheet(item: sheetBinding, onDismiss: coordinator.onModalDismiss, content: coordinator.scene(for:)) + .sheet(item: sheetBinding, onDismiss: coordinator.onModalDismiss, content: modalScene(for:)) } #else public var body: some View { TabView(selection: $coordinator.selectedTab) { content() } - .sheet(item: sheetBinding, onDismiss: coordinator.onModalDismiss, content: coordinator.scene(for:)) - .fullScreenCover(item: fullscreenCoverBinding, onDismiss: coordinator.onModalDismiss, content: coordinator.scene(for:)) + .sheet(item: sheetBinding, onDismiss: coordinator.onModalDismiss, content: modalScene(for:)) + .fullScreenCover(item: fullscreenCoverBinding, onDismiss: coordinator.onModalDismiss, content: modalScene(for:)) } #endif - private var sheetBinding: Binding { + @ViewBuilder + private func modalScene(for model: ModalCoverModel) -> some View { + if model.style.hasDetents { + coordinator.scene(for: model.destination) + .readSize { modalDetents = model.style.detents(size: $0) } + .presentationDetents(modalDetents ?? []) + } else { + coordinator.scene(for: model.destination) + } + } + + private var sheetBinding: Binding?> { .init { - coordinator.modalCover?.style == .sheet ? coordinator.modalCover?.destination : nil - } set: { destination in - coordinator.modalCover = destination.map { .init(destination: $0, style: .sheet) } + coordinator.modalCover?.style.isSheet == true ? coordinator.modalCover : nil + } set: { _ in + coordinator.modalCover = nil } } #if !os(macOS) - private var fullscreenCoverBinding: Binding { + private var fullscreenCoverBinding: Binding?> { .init { - coordinator.modalCover?.style == .fullscreenCover ? coordinator.modalCover?.destination : nil - } set: { destination in - coordinator.modalCover = destination.map { .init(destination: $0, style: .fullscreenCover) } + coordinator.modalCover?.style.isSheet == false ? coordinator.modalCover : nil + } set: { _ in + coordinator.modalCover = nil } } #endif From 47dcb866bbd1486d0a2dc9e274dfcc6cebfed13a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=CC=8Cimon=20S=CC=8Cesta=CC=81k?= Date: Sat, 2 Nov 2024 21:00:02 +0100 Subject: [PATCH 4/6] Add detents support in modal presentation of NavigationStackFlow --- .../Architecture/NavigationStackFlow.swift | 49 +++++++++++++++---- 1 file changed, 40 insertions(+), 9 deletions(-) diff --git a/Sources/FuturedArchitecture/Architecture/NavigationStackFlow.swift b/Sources/FuturedArchitecture/Architecture/NavigationStackFlow.swift index 2063dbf..40eacba 100644 --- a/Sources/FuturedArchitecture/Architecture/NavigationStackFlow.swift +++ b/Sources/FuturedArchitecture/Architecture/NavigationStackFlow.swift @@ -9,31 +9,62 @@ public struct NavigationStackFlow? + /// Use in case when whole navigation stack should have detents. + @State private var navigationDetents: Set? + + private let detents: Set? + /// - Parameters: + /// - detents: The set of detents which should be applied to the whole navigation stack. /// - coordinator: The instance of the coordinator used as the model and retained as the ``SwiftUI.StateObject`` /// - content: The root view of this navigation stack. The ``navigationDestination(for:destination:)`` modifier /// is applied to this content. - public init(coordinator: @autoclosure @escaping () -> Coordinator, content: @MainActor @escaping () -> Content) { + public init( + detents: Set? = nil, + coordinator: @autoclosure @escaping () -> Coordinator, + content: @MainActor @escaping () -> Content + ) { + self.detents = detents self._coordinator = StateObject(wrappedValue: coordinator()) self.content = content } - #if os(macOS) public var body: some View { - NavigationStack(path: $coordinator.path) { - content().navigationDestination(for: Coordinator.Destination.self, destination: coordinator.scene(for:)) + Group { + if let detents { + body(with: detents) + } else { + bodyWithoutSupportOfDetents + } } .sheet(item: sheetBinding, onDismiss: coordinator.onModalDismiss, content: modalScene(for:)) + #if !os(macOS) + .fullScreenCover(item: fullscreenCoverBinding, onDismiss: coordinator.onModalDismiss, content: modalScene(for:)) + #endif } - #else - public var body: some View { + + private var bodyWithoutSupportOfDetents: some View { NavigationStack(path: $coordinator.path) { content().navigationDestination(for: Coordinator.Destination.self, destination: coordinator.scene(for:)) } - .sheet(item: sheetBinding, onDismiss: coordinator.onModalDismiss, content: modalScene(for:)) - .fullScreenCover(item: fullscreenCoverBinding, onDismiss: coordinator.onModalDismiss, content: modalScene(for:)) } - #endif + + private func body(with detents: Set) -> some View { + NavigationStack(path: $coordinator.path) { + content() + .readSize { size in + navigationDetents = Set(detents.map { $0.detent(size: size) } ) + } + .navigationDestination(for: Coordinator.Destination.self) { destination in + coordinator.scene(for: destination) + .readSize { size in + // TODO: When detents contains .height there is a weird animation + navigationDetents = Set(detents.map { $0.detent(size: size) } ) + } + } + } + .presentationDetents(navigationDetents ?? []) + } @ViewBuilder private func modalScene(for model: ModalCoverModel) -> some View { From 59f4367f44deb1bf62699bf361962d227ac207b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=CC=8Cimon=20S=CC=8Cesta=CC=81k?= Date: Tue, 28 Jan 2025 11:58:01 +0700 Subject: [PATCH 5/6] Revert "Add detents support in modal presentation of basic views" This reverts commit ad0d927f76f24ddd3b9d55dc72365949602020df. # Conflicts: # Sources/FuturedArchitecture/Architecture/NavigationStackFlow.swift --- .../Architecture/Coordinator.swift | 4 +-- .../Architecture/ModalCoverModel.swift | 24 +------------ .../Architecture/NavigationStackFlow.swift | 34 ++++++------------ .../Architecture/TabViewFlow.swift | 36 ++++++------------- 4 files changed, 24 insertions(+), 74 deletions(-) diff --git a/Sources/FuturedArchitecture/Architecture/Coordinator.swift b/Sources/FuturedArchitecture/Architecture/Coordinator.swift index 75f095e..400797a 100644 --- a/Sources/FuturedArchitecture/Architecture/Coordinator.swift +++ b/Sources/FuturedArchitecture/Architecture/Coordinator.swift @@ -49,9 +49,9 @@ public extension Coordinator { /// - type: Kind of modal presentation. func present(modal destination: Destination, type: ModalCoverModelStyle) { switch type { - case let .sheet(detents): + case .sheet: Task { @MainActor in - self.modalCover = .init(destination: destination, style: .sheet(detents: detents)) + self.modalCover = .init(destination: destination, style: .sheet) } #if !os(macOS) case .fullscreenCover: diff --git a/Sources/FuturedArchitecture/Architecture/ModalCoverModel.swift b/Sources/FuturedArchitecture/Architecture/ModalCoverModel.swift index af1e26c..b3a61b5 100644 --- a/Sources/FuturedArchitecture/Architecture/ModalCoverModel.swift +++ b/Sources/FuturedArchitecture/Architecture/ModalCoverModel.swift @@ -34,32 +34,10 @@ public enum SheetDetent: Hashable { /// It is intended to be used with ``ModalCoverModel``. Style has been placed to /// the global scope, since the Model is generic. public enum ModalCoverModelStyle { - case sheet(detents: Set? = nil) + case sheet #if !os(macOS) case fullscreenCover #endif - - - func detents(size: CGSize) -> Set? { - if case let .sheet(detents) = self, let detents { - return Set(detents.map { $0.detent(size: size) }) - } - return nil - } - - var isSheet: Bool { - if case .sheet = self { - return true - } - return false - } - - var hasDetents: Bool { - if case let .sheet(detents) = self { - return detents != nil - } - return false - } } /// This struct is a model associating presentation style with a destination on a specific ``Coordinator``. diff --git a/Sources/FuturedArchitecture/Architecture/NavigationStackFlow.swift b/Sources/FuturedArchitecture/Architecture/NavigationStackFlow.swift index 40eacba..5c591df 100644 --- a/Sources/FuturedArchitecture/Architecture/NavigationStackFlow.swift +++ b/Sources/FuturedArchitecture/Architecture/NavigationStackFlow.swift @@ -6,9 +6,6 @@ public struct NavigationStackFlow Content - /// Use in case when modal views presented by this coordinator should have detents. - @State private var modalDetents: Set? - /// Use in case when whole navigation stack should have detents. @State private var navigationDetents: Set? @@ -37,9 +34,9 @@ public struct NavigationStackFlow) -> some View { - if model.style.hasDetents { - coordinator.scene(for: model.destination) - .readSize { modalDetents = model.style.detents(size: $0) } - .presentationDetents(modalDetents ?? []) - } else { - coordinator.scene(for: model.destination) - } - } - - private var sheetBinding: Binding?> { + private var sheetBinding: Binding { .init { - coordinator.modalCover?.style.isSheet == true ? coordinator.modalCover : nil - } set: { _ in - coordinator.modalCover = nil + coordinator.modalCover?.style == .sheet ? coordinator.modalCover?.destination : nil + } set: { destination in + coordinator.modalCover = destination.map { .init(destination: $0, style: .sheet) } } } #if !os(macOS) - private var fullscreenCoverBinding: Binding?> { + private var fullscreenCoverBinding: Binding { .init { - coordinator.modalCover?.style.isSheet == false ? coordinator.modalCover : nil - } set: { _ in - coordinator.modalCover = nil + coordinator.modalCover?.style == .fullscreenCover ? coordinator.modalCover?.destination : nil + } set: { destination in + coordinator.modalCover = destination.map { .init(destination: $0, style: .fullscreenCover) } } } #endif diff --git a/Sources/FuturedArchitecture/Architecture/TabViewFlow.swift b/Sources/FuturedArchitecture/Architecture/TabViewFlow.swift index 47fb7ac..b16c8c1 100644 --- a/Sources/FuturedArchitecture/Architecture/TabViewFlow.swift +++ b/Sources/FuturedArchitecture/Architecture/TabViewFlow.swift @@ -7,9 +7,6 @@ public struct TabViewFlow: View { @StateObject private var coordinator: Coordinator @ViewBuilder private let content: () -> Content - /// Use in case when modal views presented by this coordinator should have detents. - @State private var modalDetents: Set? - /// - Parameters: /// - coordinator: The instance of the coordinator used as the model and retained as the ``SwiftUI.StateObject`` /// - content: The definition of tabs held by this TabView should be placed into this ViewBuilder. You are required to use instances of `Tab` @@ -24,43 +21,32 @@ public struct TabViewFlow: View { TabView(selection: $coordinator.selectedTab) { content() } - .sheet(item: sheetBinding, onDismiss: coordinator.onModalDismiss, content: modalScene(for:)) + .sheet(item: sheetBinding, onDismiss: coordinator.onModalDismiss, content: coordinator.scene(for:)) } #else public var body: some View { TabView(selection: $coordinator.selectedTab) { content() } - .sheet(item: sheetBinding, onDismiss: coordinator.onModalDismiss, content: modalScene(for:)) - .fullScreenCover(item: fullscreenCoverBinding, onDismiss: coordinator.onModalDismiss, content: modalScene(for:)) + .sheet(item: sheetBinding, onDismiss: coordinator.onModalDismiss, content: coordinator.scene(for:)) + .fullScreenCover(item: fullscreenCoverBinding, onDismiss: coordinator.onModalDismiss, content: coordinator.scene(for:)) } #endif - @ViewBuilder - private func modalScene(for model: ModalCoverModel) -> some View { - if model.style.hasDetents { - coordinator.scene(for: model.destination) - .readSize { modalDetents = model.style.detents(size: $0) } - .presentationDetents(modalDetents ?? []) - } else { - coordinator.scene(for: model.destination) - } - } - - private var sheetBinding: Binding?> { + private var sheetBinding: Binding { .init { - coordinator.modalCover?.style.isSheet == true ? coordinator.modalCover : nil - } set: { _ in - coordinator.modalCover = nil + coordinator.modalCover?.style == .sheet ? coordinator.modalCover?.destination : nil + } set: { destination in + coordinator.modalCover = destination.map { .init(destination: $0, style: .sheet) } } } #if !os(macOS) - private var fullscreenCoverBinding: Binding?> { + private var fullscreenCoverBinding: Binding { .init { - coordinator.modalCover?.style.isSheet == false ? coordinator.modalCover : nil - } set: { _ in - coordinator.modalCover = nil + coordinator.modalCover?.style == .fullscreenCover ? coordinator.modalCover?.destination : nil + } set: { destination in + coordinator.modalCover = destination.map { .init(destination: $0, style: .fullscreenCover) } } } #endif From f2025f13187783f39a974f9fd88eac4bac5193b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=CC=8Cimon=20S=CC=8Cesta=CC=81k?= Date: Tue, 28 Jan 2025 12:15:17 +0700 Subject: [PATCH 6/6] Remove SheetDetent.height support --- .../Architecture/ModalCoverModel.swift | 5 +- .../Architecture/NavigationStackFlow.swift | 48 ++----------------- 2 files changed, 5 insertions(+), 48 deletions(-) diff --git a/Sources/FuturedArchitecture/Architecture/ModalCoverModel.swift b/Sources/FuturedArchitecture/Architecture/ModalCoverModel.swift index b3a61b5..59e36f9 100644 --- a/Sources/FuturedArchitecture/Architecture/ModalCoverModel.swift +++ b/Sources/FuturedArchitecture/Architecture/ModalCoverModel.swift @@ -12,17 +12,14 @@ import Foundation public enum SheetDetent: Hashable { case medium case large - case height case fraction(CGFloat) - func detent(size: CGSize) -> PresentationDetent { + func detent() -> PresentationDetent { switch self { case .medium: return .medium case .large: return .large - case .height: - return .height(size.height) case let .fraction(fraction): return .fraction(fraction) } diff --git a/Sources/FuturedArchitecture/Architecture/NavigationStackFlow.swift b/Sources/FuturedArchitecture/Architecture/NavigationStackFlow.swift index 5c591df..29bd36f 100644 --- a/Sources/FuturedArchitecture/Architecture/NavigationStackFlow.swift +++ b/Sources/FuturedArchitecture/Architecture/NavigationStackFlow.swift @@ -9,8 +9,6 @@ public struct NavigationStackFlow? - private let detents: Set? - /// - Parameters: /// - detents: The set of detents which should be applied to the whole navigation stack. /// - coordinator: The instance of the coordinator used as the model and retained as the ``SwiftUI.StateObject`` @@ -21,48 +19,22 @@ public struct NavigationStackFlow Coordinator, content: @MainActor @escaping () -> Content ) { - self.detents = detents + self.navigationDetents = detents == nil ? nil : Set(detents!.map { $0.detent() }) self._coordinator = StateObject(wrappedValue: coordinator()) self.content = content } public var body: some View { - Group { - if let detents { - body(with: detents) - } else { - bodyWithoutSupportOfDetents - } + NavigationStack(path: $coordinator.path) { + content().navigationDestination(for: Coordinator.Destination.self, destination: coordinator.scene(for:)) } + .presentationDetents(navigationDetents ?? []) .sheet(item: sheetBinding, onDismiss: coordinator.onModalDismiss, content: coordinator.scene(for:)) #if !os(macOS) .fullScreenCover(item: fullscreenCoverBinding, onDismiss: coordinator.onModalDismiss, content: coordinator.scene(for:)) #endif } - private var bodyWithoutSupportOfDetents: some View { - NavigationStack(path: $coordinator.path) { - content().navigationDestination(for: Coordinator.Destination.self, destination: coordinator.scene(for:)) - } - } - - private func body(with detents: Set) -> some View { - NavigationStack(path: $coordinator.path) { - content() - .readSize { size in - navigationDetents = Set(detents.map { $0.detent(size: size) } ) - } - .navigationDestination(for: Coordinator.Destination.self) { destination in - coordinator.scene(for: destination) - .readSize { size in - // TODO: When detents contains .height there is a weird animation - navigationDetents = Set(detents.map { $0.detent(size: size) } ) - } - } - } - .presentationDetents(navigationDetents ?? []) - } - private var sheetBinding: Binding { .init { coordinator.modalCover?.style == .sheet ? coordinator.modalCover?.destination : nil @@ -81,15 +53,3 @@ public struct NavigationStackFlow Void) -> some View { - background( - GeometryReader { proxy in - Color.clear - .onAppear { action(proxy.size) } - .onChange(of: proxy.size) { action($0) } - } - ) - } -}