From a28798bf525a7cbe1c4e05c5b2cc05757ea6c852 Mon Sep 17 00:00:00 2001 From: Shadowfacts Date: Sat, 8 Feb 2025 13:28:19 -0500 Subject: [PATCH] Remove MatchedGeometryPresentation --- Packages/ComposeUI/Package.swift | 2 - .../MatchedGeometryPresentation/.gitignore | 9 - .../MatchedGeometryPresentation/Package.swift | 29 --- .../MatchedGeometryModifiers.swift | 125 --------- .../MatchedGeometryViewController.swift | 239 ------------------ .../View+PresentViewController.swift | 62 ----- Tusker.xcodeproj/project.pbxproj | 2 - 7 files changed, 468 deletions(-) delete mode 100644 Packages/MatchedGeometryPresentation/.gitignore delete mode 100644 Packages/MatchedGeometryPresentation/Package.swift delete mode 100644 Packages/MatchedGeometryPresentation/Sources/MatchedGeometryPresentation/MatchedGeometryModifiers.swift delete mode 100644 Packages/MatchedGeometryPresentation/Sources/MatchedGeometryPresentation/MatchedGeometryViewController.swift delete mode 100644 Packages/MatchedGeometryPresentation/Sources/MatchedGeometryPresentation/View+PresentViewController.swift diff --git a/Packages/ComposeUI/Package.swift b/Packages/ComposeUI/Package.swift index 4580755d79..612c6a41fb 100644 --- a/Packages/ComposeUI/Package.swift +++ b/Packages/ComposeUI/Package.swift @@ -19,7 +19,6 @@ let package = Package( .package(path: "../Pachyderm"), .package(path: "../InstanceFeatures"), .package(path: "../TuskerComponents"), - .package(path: "../MatchedGeometryPresentation"), .package(path: "../TuskerPreferences"), .package(path: "../UserAccounts"), .package(path: "../GalleryVC"), @@ -33,7 +32,6 @@ let package = Package( "Pachyderm", "InstanceFeatures", "TuskerComponents", - "MatchedGeometryPresentation", "TuskerPreferences", "UserAccounts", "GalleryVC", diff --git a/Packages/MatchedGeometryPresentation/.gitignore b/Packages/MatchedGeometryPresentation/.gitignore deleted file mode 100644 index 3b29812086..0000000000 --- a/Packages/MatchedGeometryPresentation/.gitignore +++ /dev/null @@ -1,9 +0,0 @@ -.DS_Store -/.build -/Packages -/*.xcodeproj -xcuserdata/ -DerivedData/ -.swiftpm/config/registries.json -.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata -.netrc diff --git a/Packages/MatchedGeometryPresentation/Package.swift b/Packages/MatchedGeometryPresentation/Package.swift deleted file mode 100644 index ca4be041df..0000000000 --- a/Packages/MatchedGeometryPresentation/Package.swift +++ /dev/null @@ -1,29 +0,0 @@ -// swift-tools-version: 6.0 -// The swift-tools-version declares the minimum version of Swift required to build this package. - -import PackageDescription - -let package = Package( - name: "MatchedGeometryPresentation", - platforms: [ - .iOS(.v15), - ], - products: [ - // Products define the executables and libraries a package produces, making them visible to other packages. - .library( - name: "MatchedGeometryPresentation", - targets: ["MatchedGeometryPresentation"]), - ], - targets: [ - // Targets are the basic building blocks of a package, defining a module or a test suite. - // Targets can depend on other targets in this package and products from dependencies. - .target( - name: "MatchedGeometryPresentation", - swiftSettings: [ - .swiftLanguageMode(.v5) - ]), - // .testTarget( - // name: "MatchedGeometryPresentationTests", - // dependencies: ["MatchedGeometryPresentation"]), - ] -) diff --git a/Packages/MatchedGeometryPresentation/Sources/MatchedGeometryPresentation/MatchedGeometryModifiers.swift b/Packages/MatchedGeometryPresentation/Sources/MatchedGeometryPresentation/MatchedGeometryModifiers.swift deleted file mode 100644 index ffec5bff93..0000000000 --- a/Packages/MatchedGeometryPresentation/Sources/MatchedGeometryPresentation/MatchedGeometryModifiers.swift +++ /dev/null @@ -1,125 +0,0 @@ -// -// MatchedGeometryModifiers.swift -// MatchGeom -// -// Created by Shadowfacts on 4/24/23. -// - -import SwiftUI - -extension View { - public func matchedGeometryPresentation(id: Binding, backgroundColor: UIColor, @ViewBuilder presenting: () -> Presented) -> some View { - self.modifier(MatchedGeometryPresentationModifier(id: id, backgroundColor: backgroundColor, presented: presenting())) - } - - public func matchedGeometrySource(id: ID, presentationID: ID2) -> some View { - self.modifier(MatchedGeometrySourceModifier(id: AnyHashable(id), presentationID: AnyHashable(presentationID), matched: { AnyView(self) })) - } - - public func matchedGeometryDestination(id: ID) -> some View { - self.modifier(MatchedGeometryDestinationModifier(id: AnyHashable(id), matched: self)) - } -} - -private struct MatchedGeometryPresentationModifier: ViewModifier { - @Binding var id: ID? - let backgroundColor: UIColor - let presented: Presented - @StateObject private var state = MatchedGeometryState() - - private var isPresented: Binding { - Binding { - id != nil - } set: { - if $0 { - fatalError() - } else { - id = nil - } - } - } - - func body(content: Content) -> some View { - content - .environmentObject(state) - .backgroundPreferenceValue(MatchedGeometrySourcesKey.self, { sources in - Color.clear - .presentViewController(makeVC(allSources: sources), isPresented: isPresented) - }) - } - - private func makeVC(allSources: [SourceKey: (AnyView, CGRect)]) -> () -> UIViewController { - return { - // force unwrap is safe, this closure is only called when being presented so we must have an id - let id = AnyHashable(id!) - return MatchedGeometryViewController( - presentationID: id, - content: presented, - state: state, - backgroundColor: backgroundColor - ) - } - } -} - -private struct MatchedGeometrySourceModifier: ViewModifier { - let id: AnyHashable - let presentationID: AnyHashable - let matched: () -> AnyView - @EnvironmentObject private var state: MatchedGeometryState - - func body(content: Content) -> some View { - content - .background(GeometryReader { proxy in - Color.clear - .preference(key: MatchedGeometryDestinationFrameKey.self, value: proxy.frame(in: .global)) - .onPreferenceChange(MatchedGeometryDestinationFrameKey.self) { newValue in - if let newValue { - state.sources[SourceKey(presentationID: presentationID, matchedID: id)] = (matched, newValue) - } - } - }) - .opacity(state.animating && state.presentationID == presentationID ? 0 : 1) - } -} - -private struct MatchedGeometryDestinationModifier: ViewModifier { - let id: AnyHashable - let matched: Matched - @EnvironmentObject private var state: MatchedGeometryState - - func body(content: Content) -> some View { - content - .background(GeometryReader { proxy in - Color.clear - .preference(key: MatchedGeometryDestinationFrameKey.self, value: proxy.frame(in: .global)) - .onPreferenceChange(MatchedGeometryDestinationFrameKey.self) { newValue in - if let newValue, - // ignore intermediate layouts that may happen while the dismiss animation is happening - state.mode != .dismissing { - state.destinations[id] = (AnyView(matched), newValue) - } - } - }) - .opacity(state.animating ? 0 : 1) - } -} - -private struct MatchedGeometryDestinationFrameKey: PreferenceKey { - static let defaultValue: CGRect? = nil - static func reduce(value: inout CGRect?, nextValue: () -> CGRect?) { - value = nextValue() - } -} - -private struct MatchedGeometrySourcesKey: PreferenceKey { - static let defaultValue: [SourceKey: (AnyView, CGRect)] = [:] - static func reduce(value: inout Value, nextValue: () -> Value) { - value.merge(nextValue(), uniquingKeysWith: { _, new in new }) - } -} - -struct SourceKey: Hashable { - let presentationID: AnyHashable - let matchedID: AnyHashable -} diff --git a/Packages/MatchedGeometryPresentation/Sources/MatchedGeometryPresentation/MatchedGeometryViewController.swift b/Packages/MatchedGeometryPresentation/Sources/MatchedGeometryPresentation/MatchedGeometryViewController.swift deleted file mode 100644 index e4bd5d3d18..0000000000 --- a/Packages/MatchedGeometryPresentation/Sources/MatchedGeometryPresentation/MatchedGeometryViewController.swift +++ /dev/null @@ -1,239 +0,0 @@ -// -// MatchedGeometryViewController.swift -// MatchGeom -// -// Created by Shadowfacts on 4/24/23. -// - -import SwiftUI -import Combine - -private let mass: CGFloat = 1 -private let presentStiffness: CGFloat = 300 -private let presentDamping: CGFloat = 20 -private let dismissStiffness: CGFloat = 200 -private let dismissDamping: CGFloat = 20 - -public class MatchedGeometryState: ObservableObject { - @Published var presentationID: AnyHashable? - @Published var animating: Bool = false - @Published public var mode: Mode = .presenting - @Published var sources: [SourceKey: (() -> AnyView, CGRect)] = [:] - @Published var currentFrames: [AnyHashable: CGRect] = [:] - @Published var destinations: [AnyHashable: (AnyView, CGRect)] = [:] - - public enum Mode: Equatable { - case presenting - case idle - case dismissing - } -} - -class MatchedGeometryViewController: UIViewController, UIViewControllerTransitioningDelegate { - - let presentationID: AnyHashable - let content: Content - let state: MatchedGeometryState - let backgroundColor: UIColor - var contentHost: UIHostingController! - var matchedHost: UIHostingController! - - init(presentationID: AnyHashable, content: Content, state: MatchedGeometryState, backgroundColor: UIColor) { - self.presentationID = presentationID - self.content = content - self.state = state - self.backgroundColor = backgroundColor - - super.init(nibName: nil, bundle: nil) - - modalPresentationStyle = .custom - transitioningDelegate = self - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override func viewDidLoad() { - super.viewDidLoad() - - contentHost = UIHostingController(rootView: ContentContainerView(content: content, state: state)) - contentHost.view.autoresizingMask = [.flexibleWidth, .flexibleHeight] - contentHost.view.frame = view.bounds - contentHost.view.backgroundColor = backgroundColor - addChild(contentHost) - view.addSubview(contentHost.view) - contentHost.didMove(toParent: self) - } - - override func viewWillAppear(_ animated: Bool) { - super.viewWillAppear(animated) - - state.presentationID = presentationID - } - - var currentPresentationSources: [AnyHashable: (() -> AnyView, CGRect)] { - Dictionary(uniqueKeysWithValues: state.sources.filter { $0.key.presentationID == presentationID }.map { ($0.key.matchedID, $0.value) }) - } - - func addMatchedHostingController() { - let sources = currentPresentationSources.map { (id: $0.key, view: $0.value.0) } - matchedHost = UIHostingController(rootView: MatchedContainerView(sources: sources, state: state)) - matchedHost.view.autoresizingMask = [.flexibleWidth, .flexibleHeight] - matchedHost.view.frame = view.bounds - matchedHost.view.backgroundColor = .clear - matchedHost.view.layer.zPosition = 100 - addChild(matchedHost) - view.addSubview(matchedHost.view) - matchedHost.didMove(toParent: self) - } - - struct ContentContainerView: View { - let content: Content - let state: MatchedGeometryState - - var body: some View { - content - .environmentObject(state) - } - } - - struct MatchedContainerView: View { - let sources: [(id: AnyHashable, view: () -> AnyView)] - @ObservedObject var state: MatchedGeometryState - - var body: some View { - ZStack { - ForEach(sources, id: \.id) { (id, view) in - matchedView(id: id, source: view) - } - } - } - - @ViewBuilder - func matchedView(id: AnyHashable, source: () -> AnyView) -> some View { - if let frame = state.currentFrames[id], - let dest = state.destinations[id]?.0 { - ZStack { - source() - dest - .opacity(state.mode == .presenting ? (state.animating ? 1 : 0) : (state.animating ? 0 : 1)) - } - .frame(width: frame.width, height: frame.height) - .position(x: frame.midX, y: frame.midY) - .ignoresSafeArea() - .animation(.interpolatingSpring(mass: Double(mass), stiffness: Double(state.mode == .presenting ? presentStiffness : dismissStiffness), damping: Double(state.mode == .presenting ? presentDamping : dismissDamping), initialVelocity: 0), value: frame) - } - } - } - - // MARK: UIViewControllerTransitioningDelegate - func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? { - return MatchedGeometryPresentationAnimationController() - } - - func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? { - return MatchedGeometryDismissAnimationController() - } - - func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? { - return MatchedGeometryPresentationController(presentedViewController: presented, presenting: presenting) - } - -} - -class MatchedGeometryPresentationAnimationController: NSObject, UIViewControllerAnimatedTransitioning { - func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { - return 0.8 - } - - func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { - let matchedGeomVC = transitionContext.viewController(forKey: .to) as! MatchedGeometryViewController - let container = transitionContext.containerView - - // add the VC to the container, which kicks off layout out the content hosting controller - matchedGeomVC.view.autoresizingMask = [.flexibleWidth, .flexibleHeight] - matchedGeomVC.view.frame = container.bounds - container.addSubview(matchedGeomVC.view) - - // layout out the content hosting controller and having enough destinations may take a while - // so listen for when it's ready, rather than trying to guess at the timing - let cancellable = matchedGeomVC.state.$destinations - .filter { destinations in matchedGeomVC.currentPresentationSources.allSatisfy { source in destinations.keys.contains(source.key) } } - .first() - .sink { destinations in - matchedGeomVC.addMatchedHostingController() - - // setup the initial state for the animation - matchedGeomVC.matchedHost.view.isHidden = true - matchedGeomVC.state.mode = .presenting - matchedGeomVC.state.currentFrames = matchedGeomVC.currentPresentationSources.mapValues(\.1) - - // wait one runloop iteration for the matched hosting controller to be setup - DispatchQueue.main.async { - matchedGeomVC.matchedHost.view.isHidden = false - matchedGeomVC.state.animating = true - // get the now-current destinations, in case they've changed since the sunk value was published - matchedGeomVC.state.currentFrames = matchedGeomVC.state.destinations.mapValues(\.1) - } - } - - matchedGeomVC.contentHost.view.layer.opacity = 0 - let spring = UISpringTimingParameters(mass: mass, stiffness: presentStiffness, damping: presentDamping, initialVelocity: .zero) - let animator = UIViewPropertyAnimator(duration: self.transitionDuration(using: transitionContext), timingParameters: spring) - animator.addAnimations { - matchedGeomVC.contentHost.view.layer.opacity = 1 - } - animator.addCompletion { _ in - transitionContext.completeTransition(true) - matchedGeomVC.state.animating = false - matchedGeomVC.state.mode = .idle - - matchedGeomVC.matchedHost?.view.removeFromSuperview() - matchedGeomVC.matchedHost?.removeFromParent() - cancellable.cancel() - } - animator.startAnimation() - } -} - -class MatchedGeometryDismissAnimationController: NSObject, UIViewControllerAnimatedTransitioning { - func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { - return 0.8 - } - - func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { - let matchedGeomVC = transitionContext.viewController(forKey: .from) as! MatchedGeometryViewController - - // recreate the matched host b/c using the current destinations doesn't seem to update the existing one - matchedGeomVC.addMatchedHostingController() - matchedGeomVC.matchedHost.view.isHidden = true - matchedGeomVC.state.mode = .dismissing - matchedGeomVC.state.currentFrames = matchedGeomVC.state.destinations.mapValues(\.1) - - DispatchQueue.main.async { - matchedGeomVC.matchedHost.view.isHidden = false - matchedGeomVC.state.animating = true - matchedGeomVC.state.currentFrames = matchedGeomVC.currentPresentationSources.mapValues(\.1) - } - - let spring = UISpringTimingParameters(mass: mass, stiffness: dismissStiffness, damping: dismissDamping, initialVelocity: .zero) - let animator = UIViewPropertyAnimator(duration: transitionDuration(using: transitionContext), timingParameters: spring) - animator.addAnimations { - matchedGeomVC.contentHost.view.layer.opacity = 0 - } - animator.addCompletion { _ in - transitionContext.completeTransition(true) - matchedGeomVC.state.animating = false - matchedGeomVC.state.mode = .idle - } - animator.startAnimation() - } -} - -class MatchedGeometryPresentationController: UIPresentationController { - override func dismissalTransitionWillBegin() { - super.dismissalTransitionWillBegin() - delegate?.presentationControllerWillDismiss?(self) - } -} diff --git a/Packages/MatchedGeometryPresentation/Sources/MatchedGeometryPresentation/View+PresentViewController.swift b/Packages/MatchedGeometryPresentation/Sources/MatchedGeometryPresentation/View+PresentViewController.swift deleted file mode 100644 index 3d176d1034..0000000000 --- a/Packages/MatchedGeometryPresentation/Sources/MatchedGeometryPresentation/View+PresentViewController.swift +++ /dev/null @@ -1,62 +0,0 @@ -// -// View+PresentViewController.swift -// MatchGeom -// -// Created by Shadowfacts on 4/24/23. -// - -import SwiftUI - -extension View { - func presentViewController(_ makeVC: @escaping () -> UIViewController, isPresented: Binding) -> some View { - self - .background( - ViewControllerPresenter(makeVC: makeVC, isPresented: isPresented) - ) - } -} - -private struct ViewControllerPresenter: UIViewControllerRepresentable { - let makeVC: () -> UIViewController - @Binding var isPresented: Bool - - func makeUIViewController(context: Context) -> UIViewController { - return UIViewController() - } - - func updateUIViewController(_ uiViewController: UIViewController, context: Context) { - if isPresented { - if uiViewController.presentedViewController == nil { - let presented = makeVC() - presented.presentationController!.delegate = context.coordinator - uiViewController.present(presented, animated: true) - context.coordinator.didPresent = true - } - } else { - if context.coordinator.didPresent, - let presentedViewController = uiViewController.presentedViewController, - !presentedViewController.isBeingDismissed { - uiViewController.dismiss(animated: true) - context.coordinator.didPresent = false - } - } - } - - func makeCoordinator() -> Coordinator { - return Coordinator(isPresented: $isPresented) - } - - class Coordinator: NSObject, UIAdaptivePresentationControllerDelegate { - @Binding var isPresented: Bool - var didPresent = false - - init(isPresented: Binding) { - self._isPresented = isPresented - } - - func presentationControllerWillDismiss(_ presentationController: UIPresentationController) { - isPresented = false - didPresent = false - } - } -} diff --git a/Tusker.xcodeproj/project.pbxproj b/Tusker.xcodeproj/project.pbxproj index d25760d59e..09dd4d27d5 100644 --- a/Tusker.xcodeproj/project.pbxproj +++ b/Tusker.xcodeproj/project.pbxproj @@ -685,7 +685,6 @@ D6A6C11425B62E9700298D0F /* CacheExpiry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CacheExpiry.swift; sourceTree = ""; }; D6A6C11A25B63CEE00298D0F /* MemoryCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MemoryCache.swift; sourceTree = ""; }; D6A8D7A42C14DB280007B285 /* PersistentHistoryTokenStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PersistentHistoryTokenStore.swift; sourceTree = ""; }; - D6A9E04F29F8917500BEDC7E /* MatchedGeometryPresentation */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = MatchedGeometryPresentation; sourceTree = ""; }; D6AC956623C4347E008C9946 /* MainSceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainSceneDelegate.swift; sourceTree = ""; }; D6ADB6E728E8C878009924AB /* PublicTimelineDescriptionCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PublicTimelineDescriptionCollectionViewCell.swift; sourceTree = ""; }; D6ADB6E928E91C30009924AB /* TimelineStatusCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineStatusCollectionViewCell.swift; sourceTree = ""; }; @@ -1241,7 +1240,6 @@ D6BD395C29B789D5005FFD2B /* TuskerComponents */, D6BD395729B6441F005FFD2B /* ComposeUI */, D6CA6ED029EF6060003EC5DF /* TuskerPreferences */, - D6A9E04F29F8917500BEDC7E /* MatchedGeometryPresentation */, D642E83D2BA7AD0F004BFD6A /* GalleryVC */, D65A26242BC39A02005EB5D8 /* PushNotifications */, );