diff --git a/Sources/Transmission/Sources/Extensions/Animation+Extensions.swift b/Sources/Transmission/Sources/Extensions/Animation+Extensions.swift index fe7dfdd..0e424dd 100644 --- a/Sources/Transmission/Sources/Extensions/Animation+Extensions.swift +++ b/Sources/Transmission/Sources/Extensions/Animation+Extensions.swift @@ -192,10 +192,11 @@ extension UIViewPropertyAnimator { extension UIView { + @available(iOS, deprecated: 18.0, message: "Use the builtin UIView.animate") public static func animate( with animation: Animation?, animations: @escaping () -> Void, - completion: ((Bool) -> Void)? + completion: ((Bool) -> Void)? = nil ) { guard let animation else { animations() diff --git a/Sources/Transmission/Sources/Hosting/PresentationHostingController.swift b/Sources/Transmission/Sources/Hosting/PresentationHostingController.swift index 44fabe2..fb14c89 100644 --- a/Sources/Transmission/Sources/Hosting/PresentationHostingController.swift +++ b/Sources/Transmission/Sources/Hosting/PresentationHostingController.swift @@ -95,10 +95,12 @@ open class PresentationHostingController< } } else if tracksContentSize { - let contentSize = CGRect( - origin: .zero, - size: view.systemLayoutSizeFitting(UIView.layoutFittingExpandedSize) - ).inset(by: view.safeAreaInsets).size + var contentSize = view.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize) + if contentSize == .zero { + contentSize = view.intrinsicContentSize + } + contentSize.height -= (view.safeAreaInsets.left + view.safeAreaInsets.right) + contentSize.height -= (view.safeAreaInsets.top + view.safeAreaInsets.bottom) guard preferredContentSize != contentSize else { return } if #available(iOS 16.0, *) { if presentingViewController != nil, diff --git a/Sources/Transmission/Sources/Presentation Controllers/CardPresentationController.swift b/Sources/Transmission/Sources/Presentation Controllers/CardPresentationController.swift index ce04289..90e5f7f 100644 --- a/Sources/Transmission/Sources/Presentation Controllers/CardPresentationController.swift +++ b/Sources/Transmission/Sources/Presentation Controllers/CardPresentationController.swift @@ -91,6 +91,13 @@ class CardPresentationController: InteractivePresentationController { dimmingView.isHidden = false } + override func dismissalTransitionDidEnd(_ completed: Bool) { + super.dismissalTransitionDidEnd(completed) + if completed { + presentedViewController.view.layer.cornerRadius = 0 + } + } + override func dismissalTransitionShouldBegin( translation: CGPoint, delta: CGPoint, diff --git a/Sources/Transmission/Sources/Presentation Controllers/SlidePresentationController.swift b/Sources/Transmission/Sources/Presentation Controllers/SlidePresentationController.swift index 69ba25f..8aaaf8e 100644 --- a/Sources/Transmission/Sources/Presentation Controllers/SlidePresentationController.swift +++ b/Sources/Transmission/Sources/Presentation Controllers/SlidePresentationController.swift @@ -85,11 +85,13 @@ class SlideTransition: PresentationControllerTransition { return animator } + let frame = transitionContext.finalFrame(for: presented) #if targetEnvironment(macCatalyst) let isScaleEnabled = false #else let isTranslucentBackground = options.options.preferredPresentationBackgroundUIColor?.isTranslucent ?? false - let isScaleEnabled = options.prefersScaleEffect && !isTranslucentBackground && presenting.view.convert(presenting.view.frame.origin, to: nil).y == 0 + let isScaleEnabled = options.prefersScaleEffect && !isTranslucentBackground && presenting.view.convert(presenting.view.frame.origin, to: nil).y == 0 && + frame.origin.y == 0 #endif let safeAreaInsets = transitionContext.containerView.safeAreaInsets let cornerRadius = options.preferredCornerRadius ?? Self.displayCornerRadius @@ -122,7 +124,6 @@ class SlideTransition: PresentationControllerTransition { presenting.view.layer.masksToBounds = true presenting.view.layer.cornerCurve = .continuous - let frame = transitionContext.finalFrame(for: presented) if isPresenting { transitionContext.containerView.addSubview(presented.view) presented.view.frame = frame diff --git a/Sources/Transmission/Sources/PresentationLinkModifier.swift b/Sources/Transmission/Sources/PresentationLinkModifier.swift index e374348..b4199b9 100644 --- a/Sources/Transmission/Sources/PresentationLinkModifier.swift +++ b/Sources/Transmission/Sources/PresentationLinkModifier.swift @@ -185,6 +185,8 @@ private struct PresentationLinkModifierBody< let animation = context.transaction.animation ?? (isAnimated ? .default : nil) context.coordinator.animation = animation + var isTransitioningPresentationController = false + if let adapter = context.coordinator.adapter, !context.coordinator.isBeingReused { @@ -246,6 +248,9 @@ private struct PresentationLinkModifierBody< presentationController.edge = newValue.edge } + case (.zoom, .zoom): + break + case (.representable, .representable(let options, let transition)): if let presentationController = adapter.viewController.presentationController { func project( @@ -272,10 +277,24 @@ private struct PresentationLinkModifierBody< _openExistential(transition, do: project) } - default: + case (.default, .default), + (.currentContext, .currentContext), + (.fullscreen, .fullscreen), + (.custom, .custom): break + + default: + if context.coordinator.adapter?.transition.options.preferredPresentationBackgroundUIColor != nil { + context.coordinator.adapter?.viewController.view.backgroundColor = .systemBackground + } + context.coordinator.isBeingReused = true + isTransitioningPresentationController = true } + } + if let adapter = context.coordinator.adapter, + !context.coordinator.isBeingReused + { adapter.transition = transition.value adapter.viewController.presentationController?.overrideTraitCollection = traits @@ -353,6 +372,18 @@ private struct PresentationLinkModifierBody< context.coordinator.overrideTraitCollection = traits adapter.viewController.modalPresentationStyle = .custom + case .zoom: + if #available(iOS 18.0, *) { + adapter.viewController.preferredTransition = .zoom { [weak uiView] context in + return uiView + } + adapter.viewController.presentationController?.delegate = context.coordinator + } else { + context.coordinator.sourceView = uiView + context.coordinator.overrideTraitCollection = traits + adapter.viewController.modalPresentationStyle = .custom + } + case .representable(_, let transition): assert(!isClassType(transition), "PresentationLinkTransitionRepresentable must be value types (either a struct or an enum); it was a class") context.coordinator.sourceView = uiView @@ -366,8 +397,13 @@ private struct PresentationLinkModifierBody< adapter.viewController.modalPresentationStyle = .custom } - // Swizzle to hook up for programatic dismissal - adapter.viewController.presentationDelegate = context.coordinator + if isTransitioningPresentationController { + adapter.viewController.presentationDelegate = nil + adapter.viewController.modalTransitionStyle = .crossDissolve + } else { + // Swizzle to hook up for programatic dismissal + adapter.viewController.presentationDelegate = context.coordinator + } let present: () -> Void = { presentingViewController.present( @@ -376,14 +412,22 @@ private struct PresentationLinkModifierBody< ) { adapter.viewController .setNeedsStatusBarAppearanceUpdate(animated: isAnimated) + if isTransitioningPresentationController { + adapter.viewController.presentationDelegate = context.coordinator + } } } if let presentedViewController = presentingViewController.presentedViewController { - let shouldDismiss = presentedViewController.presentationController.map { - $0.delegate?.presentationControllerShouldDismiss?($0) ?? true - } ?? true + let shouldDismiss = + isTransitioningPresentationController || + presentedViewController.presentationController.map { + $0.delegate?.presentationControllerShouldDismiss?($0) ?? true + } ?? true if shouldDismiss { - presentedViewController.dismiss(animated: isAnimated, completion: present) + presentedViewController.dismiss( + animated: isAnimated && !isTransitioningPresentationController, + completion: present + ) } else { present() } @@ -569,6 +613,7 @@ private struct PresentationLinkModifierBody< presenting: UIViewController, source: UIViewController ) -> UIViewControllerAnimatedTransitioning? { + guard adapter?.viewController.presentationDelegate == self else { return nil } switch adapter?.transition { case .sheet(let options): #if targetEnvironment(macCatalyst) @@ -645,6 +690,7 @@ private struct PresentationLinkModifierBody< func animationController( forDismissed dismissed: UIViewController ) -> UIViewControllerAnimatedTransitioning? { + guard adapter?.viewController.presentationDelegate == self else { return nil } switch adapter?.transition { case .sheet(let options): #if targetEnvironment(macCatalyst) @@ -736,6 +782,7 @@ private struct PresentationLinkModifierBody< func interactionControllerForPresentation( using animator: UIViewControllerAnimatedTransitioning ) -> UIViewControllerInteractiveTransitioning? { + guard adapter?.viewController.presentationDelegate == self else { return nil } switch adapter?.transition { case .representable(let options, let transition): return transition.interactionControllerForPresentation( @@ -754,6 +801,7 @@ private struct PresentationLinkModifierBody< func interactionControllerForDismissal( using animator: UIViewControllerAnimatedTransitioning ) -> UIViewControllerInteractiveTransitioning? { + guard adapter?.viewController.presentationDelegate == self else { return nil } switch adapter?.transition { case .sheet: #if targetEnvironment(macCatalyst) diff --git a/Sources/Transmission/Sources/PresentationLinkTransition.swift b/Sources/Transmission/Sources/PresentationLinkTransition.swift index d437e69..a285e10 100644 --- a/Sources/Transmission/Sources/PresentationLinkTransition.swift +++ b/Sources/Transmission/Sources/PresentationLinkTransition.swift @@ -20,6 +20,7 @@ public struct PresentationLinkTransition: Sendable { case card(CardTransitionOptions) case matchedGeometry(MatchedGeometryTransitionOptions) case toast(ToastTransitionOptions) + case zoom(Options) case representable(Options, any PresentationLinkTransitionRepresentable) @available(*, deprecated) @@ -44,6 +45,7 @@ public struct PresentationLinkTransition: Sendable { case .currentContext(let options), .fullscreen(let options), .representable(let options, _), + .zoom(let options), .custom(let options, _): return options } @@ -78,6 +80,10 @@ public struct PresentationLinkTransition: Sendable { /// The toast presentation style. public static let toast = PresentationLinkTransition(value: .toast(.init())) + /// The zoom presentation style. + @available(iOS 18.0, *) + public static let zoom = PresentationLinkTransition(value: .zoom(.init())) + /// A custom presentation style. public static func custom< T: PresentationLinkTransitionRepresentable @@ -654,6 +660,13 @@ extension PresentationLinkTransition { PresentationLinkTransition(value: .toast(options)) } + @available(iOS 18.0, *) + public static func zoom( + options: Options + ) -> PresentationLinkTransition { + PresentationLinkTransition(value: .zoom(options)) + } + /// A custom presentation style. public static func custom< T: PresentationLinkTransitionRepresentable