Closed coratype closed 1 week ago
Also on a side note, is it possible for the presenting view controller to be "above" the presented view controller?
Also on a side note, is it possible for the presenting view controller to be "above" the presented view controller?
So you want to present a view behind another view? Curious to hear more about that use case. In theory that may be possible with a custom UIKit presentation controller but I'd imagine you would run into other issues
Is there a simple way to make slide transitions similar to sheets with detents instead of making them full screen? The transition would basically be exactly the same it would just come in from the top, left, right?
No simple way, as the current .slide
transition does not support detents. You would have to create your own custom transition
sorry i got side tracked
i actually created a sample of the existing slide that something close to what i want, not perfect but the idea is there. also it can be done in like 3 lines of swiftui code with tabview so
//
// Copyright (c) Nathan Tannar
//
#if os(iOS)
import SwiftUI
import UIKit
@available(iOS 14.0, *)
class SlidePresentationController: InteractivePresentationController {
var edge: Edge
override var edges: Edge.Set {
get { Edge.Set(edge) }
set {}
}
override var wantsInteractiveDismissal: Bool {
return false
}
override var presentationStyle: UIModalPresentationStyle {
.custom
}
private var depth = 0 {
didSet {
layoutPresentedView(frame: frameOfPresentedViewInContainerView)
dimmingView.alpha = depth > 0 ? 0 : 1
}
}
private var prevPresentationController: SlidePresentationController? {
presentingViewController.presentationController as? SlidePresentationController
}
init(
edge: Edge = .bottom,
presentedViewController: UIViewController,
presenting presentingViewController: UIViewController?
) {
self.edge = edge
super.init(
presentedViewController: presentedViewController,
presenting: presentingViewController
)
}
private func push() {
prevPresentationController?.depth += 1
prevPresentationController?.prevPresentationController?.depth += 1
}
private func pop() {
prevPresentationController?.depth -= 1
prevPresentationController?.prevPresentationController?.depth -= 1
}
override var frameOfPresentedViewInContainerView: CGRect {
guard let containerView = containerView else { return .zero }
let desiredWidth: CGFloat = containerView.bounds.width * 0.96
let frame = CGRect(
x: containerView.bounds.width - desiredWidth,
y: containerView.bounds.origin.y,
width: desiredWidth,
height: containerView.bounds.height
)
return frame
}
override func presentationTransitionWillBegin() {
super.presentationTransitionWillBegin()
// Set up the presented view's appearance
presentedViewController.view.layer.masksToBounds = true
presentedViewController.view.layer.cornerCurve = .continuous
// Dimming view setup
dimmingView.backgroundColor = UIColor.white.withAlphaComponent(0.1)
dimmingView.alpha = 0
dimmingView.isHidden = false
dimmingView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(dismissPresentedViewController)))
// Make sure the containerView and presenting view are available
guard let containerView = containerView, let presentingView = presentingViewController.view else { return }
// Insert dimming view above the presenting view
presentingViewController.view.insertSubview(dimmingView, aboveSubview: presentingView)
// Set the dimming view's constraints relative to the presenting view
dimmingView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
dimmingView.topAnchor.constraint(equalTo: presentingView.topAnchor),
dimmingView.bottomAnchor.constraint(equalTo: presentingView.bottomAnchor),
dimmingView.leftAnchor.constraint(equalTo: presentingView.leftAnchor),
dimmingView.rightAnchor.constraint(equalTo: presentingView.rightAnchor)
])
// Animate the dimming view alongside the presentation
if let transitionCoordinator = presentedViewController.transitionCoordinator {
transitionCoordinator.animate(alongsideTransition: { [unowned self] _ in
self.dimmingView.alpha = 1
self.push()
}, completion: { [unowned self] ctx in
self.dimmingView.alpha = ctx.isCancelled ? 0 : 1
if ctx.isCancelled {
self.pop()
}
})
}
}
override func presentedViewTransform(for translation: CGPoint) -> CGAffineTransform {
return .identity
}
override func containerViewDidLayoutSubviews() {
super.containerViewDidLayoutSubviews()
presentingViewController.view.isHidden = presentedViewController.presentedViewController != nil
}
@objc
private func dismissPresentedViewController() {
presentedViewController.dismiss(animated: true)
}
}
@available(iOS 14.0, *)
class SlideTransition: PresentationControllerTransition {
let options: PresentationLinkTransition.SlideTransitionOptions
static let displayCornerRadius: CGFloat = {
return max(UIScreen.main.displayCornerRadius, 12)
}()
init(
isPresenting: Bool,
options: PresentationLinkTransition.SlideTransitionOptions,
animation: Animation?
) {
self.options = options
super.init(isPresenting: isPresenting, animation: animation)
}
override func transitionAnimator(
using transitionContext: UIViewControllerContextTransitioning
) -> UIViewPropertyAnimator {
let isPresenting = isPresenting
let animator = UIViewPropertyAnimator(animation: animation) ?? UIViewPropertyAnimator(duration: duration, curve: completionCurve)
guard
let presented = transitionContext.viewController(forKey: isPresenting ? .to : .from),
let presenting = transitionContext.viewController(forKey: isPresenting ? .from : .to)
else {
transitionContext.completeTransition(false)
return animator
}
let safeAreaInsets = transitionContext.containerView.safeAreaInsets
let cornerRadius = options.preferredCornerRadius ?? Self.displayCornerRadius
var dzTransform = CGAffineTransform(scaleX: 0.92, y: 0.92)
switch options.edge {
case .top:
dzTransform = dzTransform.translatedBy(x: 0, y: safeAreaInsets.bottom / 2)
case .bottom:
dzTransform = dzTransform.translatedBy(x: 0, y: safeAreaInsets.top / 2)
case .leading:
switch presented.traitCollection.layoutDirection {
case .rightToLeft:
dzTransform = dzTransform.translatedBy(x: 0, y: safeAreaInsets.left / 2)
default:
dzTransform = dzTransform.translatedBy(x: 0, y: safeAreaInsets.right / 2)
}
case .trailing:
switch presented.traitCollection.layoutDirection {
case .leftToRight:
dzTransform = dzTransform.translatedBy(x: -safeAreaInsets.bottom * 0.55, y: safeAreaInsets.right / 2)
default:
dzTransform = dzTransform.translatedBy(x: 0, y: safeAreaInsets.left / 2)
}
}
print("safeAreaInsets: \(safeAreaInsets)")
presented.view.layer.masksToBounds = true
presented.view.layer.cornerCurve = .continuous
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
presented.view.transform = presentationTransform(
presented: presented,
frame: frame
)
} else {
presented.view.layer.cornerRadius = cornerRadius
}
animator.addAnimations {
if isPresenting {
presenting.view.layer.cornerRadius = cornerRadius / 3
presenting.view.transform = dzTransform
presented.view.transform = .identity
presented.view.layer.cornerRadius = cornerRadius * 0.75
} else {
presented.view.transform = self.presentationTransform(
presented: presented,
frame: frame
)
presented.view.layer.cornerRadius = 0
presenting.view.transform = .identity
presenting.view.layer.cornerRadius = cornerRadius
}
}
animator.addCompletion { animatingPosition in
switch animatingPosition {
case .end:
transitionContext.completeTransition(true)
default:
transitionContext.completeTransition(false)
}
}
return animator
}
private func presentationTransform(
presented: UIViewController,
frame: CGRect
) -> CGAffineTransform {
switch options.edge {
case .top:
return CGAffineTransform(translationX: 0, y: -frame.maxY)
case .bottom:
return CGAffineTransform(translationX: 0, y: frame.maxY)
case .leading:
switch presented.traitCollection.layoutDirection {
case .rightToLeft:
return CGAffineTransform(translationX: frame.maxX, y: 0)
default:
return CGAffineTransform(translationX: -frame.maxX, y: 0)
}
case .trailing:
switch presented.traitCollection.layoutDirection {
case .leftToRight:
return CGAffineTransform(translationX: frame.maxX, y: 0)
default:
return CGAffineTransform(translationX: -frame.maxX, y: 0)
}
}
}
}
#endif
Is there a simple way to make slide transitions similar to sheets with detents instead of making them full screen? The transition would basically be exactly the same it would just come in from the top, left, right?