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
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var showBelowSafeArea: Bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
init(draftAttachment: DraftAttachment, wrapped: any GalleryContentViewController) {
|
init(draftAttachment: DraftAttachment, wrapped: any GalleryContentViewController) {
|
||||||
self.draftAttachment = draftAttachment
|
self.draftAttachment = draftAttachment
|
||||||
self.wrapped = wrapped
|
self.wrapped = wrapped
|
||||||
@ -76,16 +80,6 @@ class AttachmentWrapperGalleryContentViewController: UIViewController, GalleryCo
|
|||||||
if !visible {
|
if !visible {
|
||||||
editDescriptionViewController.textView?.resignFirstResponder()
|
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() {
|
func galleryContentDidAppear() {
|
||||||
@ -155,7 +149,9 @@ private class EditAttachmentDescriptionViewController: UIViewController {
|
|||||||
])
|
])
|
||||||
|
|
||||||
if let wrapped {
|
if let wrapped {
|
||||||
|
addChild(wrapped)
|
||||||
stack.addArrangedSubview(wrapped.view)
|
stack.addArrangedSubview(wrapped.view)
|
||||||
|
wrapped.didMove(toParent: self)
|
||||||
}
|
}
|
||||||
|
|
||||||
textView = UITextView()
|
textView = UITextView()
|
||||||
|
@ -25,7 +25,7 @@ open class ImageGalleryContentViewController: UIViewController, GalleryContentVi
|
|||||||
private static let analyzer = ImageAnalyzer()
|
private static let analyzer = ImageAnalyzer()
|
||||||
private var _analysisInteraction: AnyObject?
|
private var _analysisInteraction: AnyObject?
|
||||||
@available(iOS 16.0, macCatalyst 17.0, *)
|
@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?) {
|
public init(image: UIImage, caption: String?, gifController: GIFController?) {
|
||||||
self.caption = caption
|
self.caption = caption
|
||||||
|
@ -18,6 +18,7 @@ public protocol GalleryContentViewController: UIViewController {
|
|||||||
var insetBottomControlsAccessoryViewControllerToSafeArea: Bool { get }
|
var insetBottomControlsAccessoryViewControllerToSafeArea: Bool { get }
|
||||||
var presentationAnimation: GalleryContentPresentationAnimation { get }
|
var presentationAnimation: GalleryContentPresentationAnimation { get }
|
||||||
var hideControlsOnZoom: Bool { get }
|
var hideControlsOnZoom: Bool { get }
|
||||||
|
var showBelowSafeArea: Bool { get }
|
||||||
|
|
||||||
func shouldHideControls() -> Bool
|
func shouldHideControls() -> Bool
|
||||||
func setControlsVisible(_ visible: Bool, animated: Bool, dueToUserInteraction: Bool)
|
func setControlsVisible(_ visible: Bool, animated: Bool, dueToUserInteraction: Bool)
|
||||||
@ -47,6 +48,10 @@ public extension GalleryContentViewController {
|
|||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var showBelowSafeArea: Bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
func shouldHideControls() -> Bool {
|
func shouldHideControls() -> Bool {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
@ -45,6 +45,7 @@ class GalleryItemViewController: UIViewController {
|
|||||||
private(set) var scrollAndZoomEnabled = true
|
private(set) var scrollAndZoomEnabled = true
|
||||||
|
|
||||||
private var scrollViewSizeForLastZoomScaleUpdate: CGSize?
|
private var scrollViewSizeForLastZoomScaleUpdate: CGSize?
|
||||||
|
private var skipScrollViewZoomUpdate = false
|
||||||
|
|
||||||
var showShareButton: Bool = true {
|
var showShareButton: Bool = true {
|
||||||
didSet {
|
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.
|
// 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
|
// 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).
|
// (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
|
scrollView.contentInsetAdjustmentBehavior = .never
|
||||||
|
|
||||||
view.addSubview(scrollView)
|
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.keyboardWillHideNotification, object: nil)
|
||||||
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillUpdate), name: UIResponder.keyboardWillChangeFrameNotification, 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() {
|
@objc private func keyboardWillUpdate() {
|
||||||
updateZoomScale(resetZoom: true)
|
updateScrollView(resetZoom: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
override func viewSafeAreaInsetsDidChange() {
|
override func viewSafeAreaInsetsDidChange() {
|
||||||
super.viewSafeAreaInsetsDidChange()
|
super.viewSafeAreaInsetsDidChange()
|
||||||
|
|
||||||
updateZoomScale(resetZoom: false)
|
updateScrollView(resetZoom: false)
|
||||||
// Ensure the transform is correct if the controls are hidden
|
// Ensure the transform is correct if the controls are hidden
|
||||||
setControlsVisible(controlsVisible, animated: false, dueToUserInteraction: false)
|
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
|
// 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 {
|
if scrollViewSizeForLastZoomScaleUpdate != scrollView.bounds.size {
|
||||||
scrollViewSizeForLastZoomScaleUpdate = scrollView.bounds.size
|
scrollViewSizeForLastZoomScaleUpdate = scrollView.bounds.size
|
||||||
updateZoomScale(resetZoom: true)
|
updateScrollView(resetZoom: true)
|
||||||
}
|
}
|
||||||
centerContent()
|
centerContent()
|
||||||
// Ensure the transform is correct if the controls are hidden and their size changed.
|
// Ensure the transform is correct if the controls are hidden and their size changed.
|
||||||
@ -293,7 +292,7 @@ class GalleryItemViewController: UIViewController {
|
|||||||
contentViewLeadingConstraint!.isActive = true
|
contentViewLeadingConstraint!.isActive = true
|
||||||
contentViewTopConstraint = content.view.topAnchor.constraint(equalTo: scrollView.topAnchor)
|
contentViewTopConstraint = content.view.topAnchor.constraint(equalTo: scrollView.topAnchor)
|
||||||
contentViewTopConstraint!.isActive = true
|
contentViewTopConstraint!.isActive = true
|
||||||
updateZoomScale(resetZoom: true)
|
updateScrollView(resetZoom: true)
|
||||||
} else {
|
} else {
|
||||||
// If the content was previously added, deactivate the old constraints.
|
// If the content was previously added, deactivate the old constraints.
|
||||||
contentViewLeadingConstraint?.isActive = false
|
contentViewLeadingConstraint?.isActive = false
|
||||||
@ -332,6 +331,13 @@ class GalleryItemViewController: UIViewController {
|
|||||||
topControlsView.transform = CGAffineTransform(translationX: 0, y: visible ? 0 : -topControlsView.bounds.height)
|
topControlsView.transform = CGAffineTransform(translationX: 0, y: visible ? 0 : -topControlsView.bounds.height)
|
||||||
bottomControlsView.transform = CGAffineTransform(translationX: 0, y: visible ? 0 : bottomControlsView.bounds.height)
|
bottomControlsView.transform = CGAffineTransform(translationX: 0, y: visible ? 0 : bottomControlsView.bounds.height)
|
||||||
content.setControlsVisible(visible, animated: animated, dueToUserInteraction: dueToUserInteraction)
|
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 {
|
if animated {
|
||||||
let animator = UIViewPropertyAnimator(duration: 0.2, timingParameters: UISpringTimingParameters())
|
let animator = UIViewPropertyAnimator(duration: 0.2, timingParameters: UISpringTimingParameters())
|
||||||
@ -344,7 +350,7 @@ class GalleryItemViewController: UIViewController {
|
|||||||
setNeedsUpdateOfHomeIndicatorAutoHidden()
|
setNeedsUpdateOfHomeIndicatorAutoHidden()
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateZoomScale(resetZoom: Bool) {
|
func updateScrollView(resetZoom: Bool) {
|
||||||
scrollView.contentSize = content.contentSize
|
scrollView.contentSize = content.contentSize
|
||||||
|
|
||||||
guard scrollAndZoomEnabled else {
|
guard scrollAndZoomEnabled else {
|
||||||
@ -358,17 +364,7 @@ class GalleryItemViewController: UIViewController {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Post-iOS 17, we can ask the keyboard layout guide to ignore the bottom safe area.
|
let heightScale = availableHeightForContent() / content.contentSize.height
|
||||||
// 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 widthScale = view.bounds.width / content.contentSize.width
|
let widthScale = view.bounds.width / content.contentSize.width
|
||||||
let minScale = min(widthScale, heightScale)
|
let minScale = min(widthScale, heightScale)
|
||||||
let maxScale = minScale >= 1 ? minScale + 2 : 2
|
let maxScale = minScale >= 1 ? minScale + 2 : 2
|
||||||
@ -381,6 +377,20 @@ class GalleryItemViewController: UIViewController {
|
|||||||
scrollView.zoomScale = max(minScale, min(maxScale, scrollView.zoomScale))
|
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()
|
centerContent()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -389,15 +399,29 @@ class GalleryItemViewController: UIViewController {
|
|||||||
return
|
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
|
// 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.
|
// 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)
|
let yOffset = max(0, (availableHeightForContent() - content.view.frame.height) / 2)
|
||||||
contentViewTopConstraint!.constant = yOffset
|
contentViewTopConstraint!.constant = yOffset + additionalYOffset
|
||||||
|
|
||||||
let xOffset = max(0, (view.bounds.width - content.view.frame.width) / 2)
|
let xOffset = max(0, (view.bounds.width - content.view.frame.width) / 2)
|
||||||
contentViewLeadingConstraint!.constant = xOffset
|
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() {
|
private func updateShareButton() {
|
||||||
shareButton.isEnabled = !content.activityItemsForSharing.isEmpty
|
shareButton.isEnabled = !content.activityItemsForSharing.isEmpty
|
||||||
}
|
}
|
||||||
@ -580,14 +604,14 @@ extension GalleryItemViewController: GalleryContentViewControllerContainer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func galleryContentChanged() {
|
func galleryContentChanged() {
|
||||||
updateZoomScale(resetZoom: true)
|
updateScrollView(resetZoom: true)
|
||||||
updateShareButton()
|
updateShareButton()
|
||||||
updateCaptionTextView()
|
updateCaptionTextView()
|
||||||
}
|
}
|
||||||
|
|
||||||
func disableGalleryScrollAndZoom() {
|
func disableGalleryScrollAndZoom() {
|
||||||
scrollAndZoomEnabled = false
|
scrollAndZoomEnabled = false
|
||||||
updateZoomScale(resetZoom: true)
|
updateScrollView(resetZoom: true)
|
||||||
scrollView.isScrollEnabled = false
|
scrollView.isScrollEnabled = false
|
||||||
// Make sure the content is re-added with the correct constraints
|
// Make sure the content is re-added with the correct constraints
|
||||||
if content.parent == self {
|
if content.parent == self {
|
||||||
@ -610,6 +634,10 @@ extension GalleryItemViewController: UIScrollViewDelegate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func scrollViewDidZoom(_ scrollView: UIScrollView) {
|
func scrollViewDidZoom(_ scrollView: UIScrollView) {
|
||||||
|
guard !skipScrollViewZoomUpdate else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if scrollView.zoomScale <= scrollView.minimumZoomScale {
|
if scrollView.zoomScale <= scrollView.minimumZoomScale {
|
||||||
setControlsVisible(true, animated: true, dueToUserInteraction: true)
|
setControlsVisible(true, animated: true, dueToUserInteraction: true)
|
||||||
} else {
|
} else {
|
||||||
|
@ -56,7 +56,7 @@ class GalleryPresentationAnimationController: NSObject, UIViewControllerAnimated
|
|||||||
|
|
||||||
container.layoutIfNeeded()
|
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.
|
// 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 sourceFrameInContainer = container.convert(sourceView.bounds, from: sourceView)
|
||||||
let destFrameInContainer = container.convert(itemViewController.content.view.bounds, from: itemViewController.content.view)
|
let destFrameInContainer = container.convert(itemViewController.content.view.bounds, from: itemViewController.content.view)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user