Inset gallery content to safe area when editing attachment description
This commit is contained in:
parent
86e1403230
commit
64c377c663
@ -44,6 +44,10 @@ class AttachmentWrapperGalleryContentViewController: UIViewController, GalleryCo
|
||||
false
|
||||
}
|
||||
|
||||
var showBelowSafeArea: Bool {
|
||||
false
|
||||
}
|
||||
|
||||
init(draftAttachment: DraftAttachment, wrapped: any GalleryContentViewController) {
|
||||
self.draftAttachment = draftAttachment
|
||||
self.wrapped = wrapped
|
||||
@ -76,16 +80,6 @@ class AttachmentWrapperGalleryContentViewController: UIViewController, GalleryCo
|
||||
if !visible {
|
||||
editDescriptionViewController.textView?.resignFirstResponder()
|
||||
}
|
||||
if #available(iOS 16.0, macCatalyst 17.0, *),
|
||||
let wrapped = wrapped as? ImageGalleryContentViewController,
|
||||
let interaction = wrapped.analysisInteraction {
|
||||
if visible {
|
||||
let bottom = editDescriptionViewController.view.bounds.height - editDescriptionViewController.view.keyboardLayoutGuide.layoutFrame.height
|
||||
interaction.supplementaryInterfaceContentInsets = UIEdgeInsets(top: 0, left: 0, bottom: bottom, right: 0)
|
||||
} else {
|
||||
interaction.supplementaryInterfaceContentInsets = .zero
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func galleryContentDidAppear() {
|
||||
@ -155,7 +149,9 @@ private class EditAttachmentDescriptionViewController: UIViewController {
|
||||
])
|
||||
|
||||
if let wrapped {
|
||||
addChild(wrapped)
|
||||
stack.addArrangedSubview(wrapped.view)
|
||||
wrapped.didMove(toParent: self)
|
||||
}
|
||||
|
||||
textView = UITextView()
|
||||
|
@ -25,7 +25,7 @@ open class ImageGalleryContentViewController: UIViewController, GalleryContentVi
|
||||
private static let analyzer = ImageAnalyzer()
|
||||
private var _analysisInteraction: AnyObject?
|
||||
@available(iOS 16.0, macCatalyst 17.0, *)
|
||||
public var analysisInteraction: ImageAnalysisInteraction? { _analysisInteraction as? ImageAnalysisInteraction }
|
||||
private var analysisInteraction: ImageAnalysisInteraction? { _analysisInteraction as? ImageAnalysisInteraction }
|
||||
|
||||
public init(image: UIImage, caption: String?, gifController: GIFController?) {
|
||||
self.caption = caption
|
||||
|
@ -18,6 +18,7 @@ public protocol GalleryContentViewController: UIViewController {
|
||||
var insetBottomControlsAccessoryViewControllerToSafeArea: Bool { get }
|
||||
var presentationAnimation: GalleryContentPresentationAnimation { get }
|
||||
var hideControlsOnZoom: Bool { get }
|
||||
var showBelowSafeArea: Bool { get }
|
||||
|
||||
func shouldHideControls() -> Bool
|
||||
func setControlsVisible(_ visible: Bool, animated: Bool, dueToUserInteraction: Bool)
|
||||
@ -47,6 +48,10 @@ public extension GalleryContentViewController {
|
||||
true
|
||||
}
|
||||
|
||||
var showBelowSafeArea: Bool {
|
||||
true
|
||||
}
|
||||
|
||||
func shouldHideControls() -> Bool {
|
||||
true
|
||||
}
|
||||
|
@ -45,6 +45,7 @@ class GalleryItemViewController: UIViewController {
|
||||
private(set) var scrollAndZoomEnabled = true
|
||||
|
||||
private var scrollViewSizeForLastZoomScaleUpdate: CGSize?
|
||||
private var skipScrollViewZoomUpdate = false
|
||||
|
||||
var showShareButton: Bool = true {
|
||||
didSet {
|
||||
@ -83,6 +84,7 @@ class GalleryItemViewController: UIViewController {
|
||||
// We calculate zoom/position ignoring the safe area, so content insets need to not incorporate it either.
|
||||
// Otherwise, content that fills the screen (extending into the safe area) may still end up scrollable
|
||||
// (this is readily observable with tall images on a landscape iPad).
|
||||
// Even if the content is not being shown below the safe area, we still set this to make the calculations more consistent/straightforward.
|
||||
scrollView.contentInsetAdjustmentBehavior = .never
|
||||
|
||||
view.addSubview(scrollView)
|
||||
@ -229,19 +231,16 @@ class GalleryItemViewController: UIViewController {
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillUpdate), name: UIResponder.keyboardWillHideNotification, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillUpdate), name: UIResponder.keyboardWillChangeFrameNotification, object: nil)
|
||||
|
||||
if #available(iOS 17.0, *) {
|
||||
view.keyboardLayoutGuide.usesBottomSafeArea = false
|
||||
}
|
||||
}
|
||||
|
||||
@objc private func keyboardWillUpdate() {
|
||||
updateZoomScale(resetZoom: true)
|
||||
updateScrollView(resetZoom: true)
|
||||
}
|
||||
|
||||
override func viewSafeAreaInsetsDidChange() {
|
||||
super.viewSafeAreaInsetsDidChange()
|
||||
|
||||
updateZoomScale(resetZoom: false)
|
||||
updateScrollView(resetZoom: false)
|
||||
// Ensure the transform is correct if the controls are hidden
|
||||
setControlsVisible(controlsVisible, animated: false, dueToUserInteraction: false)
|
||||
|
||||
@ -255,7 +254,7 @@ class GalleryItemViewController: UIViewController {
|
||||
// This might also fix an issue on macOS (Designed for iPad) where the content isn't placed correctly. See #446
|
||||
if scrollViewSizeForLastZoomScaleUpdate != scrollView.bounds.size {
|
||||
scrollViewSizeForLastZoomScaleUpdate = scrollView.bounds.size
|
||||
updateZoomScale(resetZoom: true)
|
||||
updateScrollView(resetZoom: true)
|
||||
}
|
||||
centerContent()
|
||||
// Ensure the transform is correct if the controls are hidden and their size changed.
|
||||
@ -293,7 +292,7 @@ class GalleryItemViewController: UIViewController {
|
||||
contentViewLeadingConstraint!.isActive = true
|
||||
contentViewTopConstraint = content.view.topAnchor.constraint(equalTo: scrollView.topAnchor)
|
||||
contentViewTopConstraint!.isActive = true
|
||||
updateZoomScale(resetZoom: true)
|
||||
updateScrollView(resetZoom: true)
|
||||
} else {
|
||||
// If the content was previously added, deactivate the old constraints.
|
||||
contentViewLeadingConstraint?.isActive = false
|
||||
@ -332,6 +331,13 @@ class GalleryItemViewController: UIViewController {
|
||||
topControlsView.transform = CGAffineTransform(translationX: 0, y: visible ? 0 : -topControlsView.bounds.height)
|
||||
bottomControlsView.transform = CGAffineTransform(translationX: 0, y: visible ? 0 : bottomControlsView.bounds.height)
|
||||
content.setControlsVisible(visible, animated: animated, dueToUserInteraction: dueToUserInteraction)
|
||||
|
||||
if !content.showBelowSafeArea {
|
||||
skipScrollViewZoomUpdate = true
|
||||
updateScrollView(resetZoom: abs(scrollView.zoomScale - scrollView.minimumZoomScale) < 0.01)
|
||||
skipScrollViewZoomUpdate = false
|
||||
scrollView.layoutIfNeeded()
|
||||
}
|
||||
}
|
||||
if animated {
|
||||
let animator = UIViewPropertyAnimator(duration: 0.2, timingParameters: UISpringTimingParameters())
|
||||
@ -344,7 +350,7 @@ class GalleryItemViewController: UIViewController {
|
||||
setNeedsUpdateOfHomeIndicatorAutoHidden()
|
||||
}
|
||||
|
||||
func updateZoomScale(resetZoom: Bool) {
|
||||
func updateScrollView(resetZoom: Bool) {
|
||||
scrollView.contentSize = content.contentSize
|
||||
|
||||
guard scrollAndZoomEnabled else {
|
||||
@ -358,17 +364,7 @@ class GalleryItemViewController: UIViewController {
|
||||
return
|
||||
}
|
||||
|
||||
// Post-iOS 17, we can ask the keyboard layout guide to ignore the bottom safe area.
|
||||
// Pre, we have to do that ourselves.
|
||||
let keyboardHeight: CGFloat
|
||||
if #available(iOS 17.0, *) {
|
||||
keyboardHeight = view.keyboardLayoutGuide.layoutFrame.height
|
||||
} else {
|
||||
let bottomSafeArea = view.bounds.height - view.safeAreaLayoutGuide.layoutFrame.maxY
|
||||
let rawKeyboardHeight = view.keyboardLayoutGuide.layoutFrame.height
|
||||
keyboardHeight = abs(rawKeyboardHeight - bottomSafeArea) < 1 ? 0 : rawKeyboardHeight
|
||||
}
|
||||
let heightScale = (view.bounds.height - keyboardHeight) / content.contentSize.height
|
||||
let heightScale = availableHeightForContent() / content.contentSize.height
|
||||
let widthScale = view.bounds.width / content.contentSize.width
|
||||
let minScale = min(widthScale, heightScale)
|
||||
let maxScale = minScale >= 1 ? minScale + 2 : 2
|
||||
@ -381,6 +377,20 @@ class GalleryItemViewController: UIViewController {
|
||||
scrollView.zoomScale = max(minScale, min(maxScale, scrollView.zoomScale))
|
||||
}
|
||||
|
||||
let bottomInset: CGFloat
|
||||
let bottomIndicatorInset: CGFloat
|
||||
if !content.showBelowSafeArea,
|
||||
controlsVisible,
|
||||
let bottomControlsView {
|
||||
bottomInset = bottomControlsView.bounds.height + view.safeAreaInsets.top
|
||||
bottomIndicatorInset = bottomControlsView.safeAreaLayoutGuide.layoutFrame.height
|
||||
} else {
|
||||
bottomInset = 0
|
||||
bottomIndicatorInset = 0
|
||||
}
|
||||
scrollView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: bottomInset, right: 0)
|
||||
scrollView.verticalScrollIndicatorInsets = UIEdgeInsets(top: 0, left: 0, bottom: bottomIndicatorInset, right: 0)
|
||||
|
||||
centerContent()
|
||||
}
|
||||
|
||||
@ -389,15 +399,29 @@ class GalleryItemViewController: UIViewController {
|
||||
return
|
||||
}
|
||||
|
||||
let additionalYOffset = content.showBelowSafeArea ? 0 : view.safeAreaInsets.top
|
||||
// Note: use frame for the content.view, because that's in the coordinate space of the scroll view
|
||||
// which means it's already been scaled by the zoom factor.
|
||||
let yOffset = max(0, (view.bounds.height - view.keyboardLayoutGuide.layoutFrame.height - content.view.frame.height) / 2)
|
||||
contentViewTopConstraint!.constant = yOffset
|
||||
let yOffset = max(0, (availableHeightForContent() - content.view.frame.height) / 2)
|
||||
contentViewTopConstraint!.constant = yOffset + additionalYOffset
|
||||
|
||||
let xOffset = max(0, (view.bounds.width - content.view.frame.width) / 2)
|
||||
contentViewLeadingConstraint!.constant = xOffset
|
||||
}
|
||||
|
||||
private func availableHeightForContent() -> CGFloat {
|
||||
var availableHeight: CGFloat
|
||||
if content.showBelowSafeArea {
|
||||
availableHeight = view.bounds.height
|
||||
} else {
|
||||
availableHeight = view.safeAreaLayoutGuide.layoutFrame.height
|
||||
if controlsVisible {
|
||||
availableHeight -= bottomControlsView?.safeAreaLayoutGuide.layoutFrame.height ?? 0
|
||||
}
|
||||
}
|
||||
return availableHeight
|
||||
}
|
||||
|
||||
private func updateShareButton() {
|
||||
shareButton.isEnabled = !content.activityItemsForSharing.isEmpty
|
||||
}
|
||||
@ -580,14 +604,14 @@ extension GalleryItemViewController: GalleryContentViewControllerContainer {
|
||||
}
|
||||
|
||||
func galleryContentChanged() {
|
||||
updateZoomScale(resetZoom: true)
|
||||
updateScrollView(resetZoom: true)
|
||||
updateShareButton()
|
||||
updateCaptionTextView()
|
||||
}
|
||||
|
||||
func disableGalleryScrollAndZoom() {
|
||||
scrollAndZoomEnabled = false
|
||||
updateZoomScale(resetZoom: true)
|
||||
updateScrollView(resetZoom: true)
|
||||
scrollView.isScrollEnabled = false
|
||||
// Make sure the content is re-added with the correct constraints
|
||||
if content.parent == self {
|
||||
@ -610,6 +634,10 @@ extension GalleryItemViewController: UIScrollViewDelegate {
|
||||
}
|
||||
|
||||
func scrollViewDidZoom(_ scrollView: UIScrollView) {
|
||||
guard !skipScrollViewZoomUpdate else {
|
||||
return
|
||||
}
|
||||
|
||||
if scrollView.zoomScale <= scrollView.minimumZoomScale {
|
||||
setControlsVisible(true, animated: true, dueToUserInteraction: true)
|
||||
} else {
|
||||
|
@ -56,7 +56,7 @@ class GalleryPresentationAnimationController: NSObject, UIViewControllerAnimated
|
||||
|
||||
container.layoutIfNeeded()
|
||||
// Make sure the zoom scale is updated before getting the content view frame, since it needs to take into account the correct transform.
|
||||
itemViewController.updateZoomScale(resetZoom: true)
|
||||
itemViewController.updateScrollView(resetZoom: true)
|
||||
|
||||
let sourceFrameInContainer = container.convert(sourceView.bounds, from: sourceView)
|
||||
let destFrameInContainer = container.convert(itemViewController.content.view.bounds, from: itemViewController.content.view)
|
||||
|
Loading…
x
Reference in New Issue
Block a user