Fix multi-attachment layout issues
This commit is contained in:
parent
55fc032f36
commit
85ed53b990
|
@ -62,7 +62,7 @@ class AttachmentViewController: UIViewController {
|
|||
|
||||
func createLargeImage(data: Data) {
|
||||
guard let image = UIImage(data: data) else { return }
|
||||
largeImageVC = LargeImageViewController(image: image, description: attachment.description, sourceFrame: nil, sourceCornerRadius: nil)
|
||||
largeImageVC = LargeImageViewController(image: image, description: attachment.description, sourceInfo: nil)
|
||||
largeImageVC!.initialControlsVisible = initialControlsVisible
|
||||
largeImageVC!.shrinkGestureEnabled = false
|
||||
if attachment.url.pathExtension == "gif" {
|
||||
|
|
|
@ -13,7 +13,7 @@ class GalleryViewController: UIPageViewController, UIPageViewControllerDataSourc
|
|||
var dismissInteractionController: LargeImageInteractionController?
|
||||
|
||||
let attachments: [Attachment]
|
||||
let sourcesInfo: [(CGRect, CGFloat)]
|
||||
let sourcesInfo: [LargeImageViewController.SourceInfo?]
|
||||
let startIndex: Int
|
||||
|
||||
let pages: [AttachmentViewController]
|
||||
|
@ -34,7 +34,7 @@ class GalleryViewController: UIPageViewController, UIPageViewControllerDataSourc
|
|||
viewControllers?.first
|
||||
}
|
||||
|
||||
init(attachments: [Attachment], sourcesInfo: [(CGRect, CGFloat)], startIndex: Int) {
|
||||
init(attachments: [Attachment], sourcesInfo: [LargeImageViewController.SourceInfo?], startIndex: Int) {
|
||||
self.attachments = attachments
|
||||
self.sourcesInfo = sourcesInfo
|
||||
self.startIndex = startIndex
|
||||
|
|
|
@ -19,11 +19,16 @@ class GalleryExpandAnimationController: NSObject, UIViewControllerAnimatedTransi
|
|||
let toVC = transitionContext.viewController(forKey: .to) as? GalleryViewController else {
|
||||
return
|
||||
}
|
||||
let attachment = toVC.attachments[toVC.startIndex]
|
||||
let (sourceFrame, sourceCornerRadius) = toVC.sourcesInfo[toVC.startIndex]
|
||||
|
||||
let finalVCFrame = transitionContext.finalFrame(for: toVC)
|
||||
guard let (sourceFrame, sourceCornerRadius) = toVC.sourcesInfo[toVC.startIndex] else {
|
||||
toVC.view.frame = finalVCFrame
|
||||
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
|
||||
return
|
||||
}
|
||||
|
||||
let attachment = toVC.attachments[toVC.startIndex]
|
||||
|
||||
guard let data = ImageCache.attachments.get(attachment.url), let image = UIImage(data: data) else {
|
||||
toVC.view.frame = finalVCFrame
|
||||
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
|
||||
|
|
|
@ -26,7 +26,10 @@ class GalleryShrinkAnimationController: NSObject, UIViewControllerAnimatedTransi
|
|||
return
|
||||
}
|
||||
|
||||
let (sourceFrame, sourceCornerRadius) = fromVC.sourcesInfo[fromVC.currentIndex]
|
||||
guard let (sourceFrame, sourceCornerRadius) = fromVC.sourcesInfo[fromVC.currentIndex] else {
|
||||
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
|
||||
return
|
||||
}
|
||||
let originalVCFrame = fromVC.view.frame
|
||||
|
||||
let attachment = fromVC.attachments[fromVC.currentIndex]
|
||||
|
|
|
@ -13,8 +13,9 @@ import Gifu
|
|||
|
||||
class LargeImageViewController: UIViewController, UIScrollViewDelegate {
|
||||
|
||||
var originFrame: CGRect?
|
||||
var originCornerRadius: CGFloat?
|
||||
typealias SourceInfo = (frame: CGRect, cornerRadius: CGFloat)
|
||||
|
||||
var sourceInfo: SourceInfo?
|
||||
var dismissInteractionController: LargeImageInteractionController?
|
||||
|
||||
@IBOutlet weak var scrollView: UIScrollView!
|
||||
|
@ -60,12 +61,11 @@ class LargeImageViewController: UIViewController, UIScrollViewDelegate {
|
|||
return !controlsVisible
|
||||
}
|
||||
|
||||
init(image: UIImage, description: String?, sourceFrame: CGRect?, sourceCornerRadius: CGFloat?) {
|
||||
init(image: UIImage, description: String?, sourceInfo: SourceInfo?) {
|
||||
self.image = image
|
||||
self.imageDescription = description
|
||||
self.originFrame = sourceFrame
|
||||
self.originCornerRadius = sourceCornerRadius
|
||||
|
||||
self.sourceInfo = sourceInfo
|
||||
|
||||
super.init(nibName: "LargeImageViewController", bundle: nil)
|
||||
|
||||
modalPresentationStyle = .fullScreen
|
||||
|
|
|
@ -17,13 +17,18 @@ class LargeImageExpandAnimationController: NSObject, UIViewControllerAnimatedTra
|
|||
|
||||
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
|
||||
guard let fromVC = transitionContext.viewController(forKey: .from),
|
||||
let toVC = transitionContext.viewController(forKey: .to) as? LargeImageViewController,
|
||||
let originFrame = toVC.originFrame else {
|
||||
let toVC = transitionContext.viewController(forKey: .to) as? LargeImageViewController else {
|
||||
return
|
||||
}
|
||||
|
||||
let containerView = transitionContext.containerView
|
||||
let finalVCFrame = transitionContext.finalFrame(for: toVC)
|
||||
guard let (originFrame, originCornerRadius) = toVC.sourceInfo else {
|
||||
toVC.view.frame = finalVCFrame
|
||||
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
|
||||
return
|
||||
}
|
||||
|
||||
let containerView = transitionContext.containerView
|
||||
let image = toVC.imageView.image!
|
||||
let ratio = image.size.width / image.size.height
|
||||
let width = finalVCFrame.width
|
||||
|
@ -36,7 +41,7 @@ class LargeImageExpandAnimationController: NSObject, UIViewControllerAnimatedTra
|
|||
imageView.animate(withGIFData: gifData)
|
||||
}
|
||||
imageView.contentMode = .scaleAspectFill
|
||||
imageView.layer.cornerRadius = toVC.originCornerRadius!
|
||||
imageView.layer.cornerRadius = originCornerRadius
|
||||
imageView.layer.masksToBounds = true
|
||||
|
||||
let blackView = UIView(frame: finalVCFrame)
|
||||
|
|
|
@ -23,11 +23,15 @@ class LargeImageShrinkAnimationController: NSObject, UIViewControllerAnimatedTra
|
|||
|
||||
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
|
||||
guard let fromVC = transitionContext.viewController(forKey: .from) as? LargeImageViewController,
|
||||
let toVC = transitionContext.viewController(forKey: .to),
|
||||
let finalFrame = fromVC.originFrame else {
|
||||
let toVC = transitionContext.viewController(forKey: .to) else {
|
||||
return
|
||||
}
|
||||
|
||||
guard let (finalFrame, finalCornerRadius) = fromVC.sourceInfo else {
|
||||
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
|
||||
return
|
||||
}
|
||||
|
||||
let originalVCFrame = fromVC.view.frame
|
||||
|
||||
let containerView = transitionContext.containerView
|
||||
|
@ -57,7 +61,7 @@ class LargeImageShrinkAnimationController: NSObject, UIViewControllerAnimatedTra
|
|||
let duration = transitionDuration(using: transitionContext)
|
||||
UIView.animate(withDuration: duration, animations: {
|
||||
imageView.frame = finalFrame
|
||||
imageView.layer.cornerRadius = fromVC.originCornerRadius!
|
||||
imageView.layer.cornerRadius = finalCornerRadius
|
||||
blackView.alpha = 0
|
||||
}, completion: { _ in
|
||||
blackView.removeFromSuperview()
|
||||
|
|
|
@ -34,9 +34,9 @@ protocol TuskerNavigationDelegate {
|
|||
|
||||
func showLargeImage(gifData: Data, description: String?, animatingFrom sourceView: UIView)
|
||||
|
||||
func gallery(attachments: [Attachment], sourceViews: [UIView], startIndex: Int) -> GalleryViewController
|
||||
func gallery(attachments: [Attachment], sourceViews: [UIView?], startIndex: Int) -> GalleryViewController
|
||||
|
||||
func showGallery(attachments: [Attachment], sourceViews: [UIView], startIndex: Int)
|
||||
func showGallery(attachments: [Attachment], sourceViews: [UIView?], startIndex: Int)
|
||||
|
||||
func showMoreOptions(forStatus statusID: String)
|
||||
|
||||
|
@ -99,7 +99,9 @@ extension TuskerNavigationDelegate where Self: UIViewController {
|
|||
present(vc, animated: true)
|
||||
}
|
||||
|
||||
private func sourceViewInfo(_ sourceView: UIView) -> (CGRect, CGFloat) {
|
||||
private func sourceViewInfo(_ sourceView: UIView?) -> LargeImageViewController.SourceInfo? {
|
||||
guard let sourceView = sourceView else { return nil }
|
||||
|
||||
var sourceFrame = sourceView.convert(sourceView.bounds, to: view)
|
||||
if let scrollView = view as? UIScrollView {
|
||||
let scale = scrollView.zoomScale
|
||||
|
@ -109,19 +111,17 @@ extension TuskerNavigationDelegate where Self: UIViewController {
|
|||
let y = sourceFrame.minY * scale - scrollView.contentOffset.y + scrollView.frame.minY
|
||||
sourceFrame = CGRect(x: x, y: y, width: width, height: height)
|
||||
}
|
||||
return (sourceFrame, sourceView.layer.cornerRadius)
|
||||
return (frame: sourceFrame, cornerRadius: sourceView.layer.cornerRadius)
|
||||
}
|
||||
|
||||
func largeImage(_ image: UIImage, description: String?, sourceView: UIView) -> LargeImageViewController {
|
||||
let (sourceFrame, sourceCornerRadius) = sourceViewInfo(sourceView)
|
||||
let vc = LargeImageViewController(image: image, description: description, sourceFrame: sourceFrame, sourceCornerRadius: sourceCornerRadius)
|
||||
let vc = LargeImageViewController(image: image, description: description, sourceInfo: sourceViewInfo(sourceView))
|
||||
vc.transitioningDelegate = self
|
||||
return vc
|
||||
}
|
||||
|
||||
func largeImage(gifData: Data, description: String?, sourceView: UIView) -> LargeImageViewController {
|
||||
let (sourceFrame, sourceCornerRadius) = sourceViewInfo(sourceView)
|
||||
let vc = LargeImageViewController(image: UIImage(data: gifData)!, description: description, sourceFrame: sourceFrame, sourceCornerRadius: sourceCornerRadius)
|
||||
let vc = LargeImageViewController(image: UIImage(data: gifData)!, description: description, sourceInfo: sourceViewInfo(sourceView))
|
||||
vc.transitioningDelegate = self
|
||||
vc.gifData = gifData
|
||||
return vc
|
||||
|
@ -135,14 +135,14 @@ extension TuskerNavigationDelegate where Self: UIViewController {
|
|||
present(largeImage(gifData: gifData, description: description, sourceView: sourceView), animated: true)
|
||||
}
|
||||
|
||||
func gallery(attachments: [Attachment], sourceViews: [UIView], startIndex: Int) -> GalleryViewController {
|
||||
func gallery(attachments: [Attachment], sourceViews: [UIView?], startIndex: Int) -> GalleryViewController {
|
||||
let sourcesInfo = sourceViews.map(sourceViewInfo)
|
||||
let vc = GalleryViewController(attachments: attachments, sourcesInfo: sourcesInfo, startIndex: startIndex)
|
||||
vc.transitioningDelegate = self
|
||||
return vc
|
||||
}
|
||||
|
||||
func showGallery(attachments: [Attachment], sourceViews: [UIView], startIndex: Int) {
|
||||
func showGallery(attachments: [Attachment], sourceViews: [UIView?], startIndex: Int) {
|
||||
present(gallery(attachments: attachments, sourceViews: sourceViews, startIndex: startIndex), animated: true)
|
||||
}
|
||||
|
||||
|
|
|
@ -13,60 +13,79 @@ class AttachmentsContainerView: UIView {
|
|||
|
||||
var delegate: AttachmentViewDelegate?
|
||||
|
||||
let attachmentViews: NSHashTable<AttachmentView> = .weakObjects()
|
||||
|
||||
override func awakeFromNib() {
|
||||
super.awakeFromNib()
|
||||
|
||||
self.isUserInteractionEnabled = true
|
||||
}
|
||||
|
||||
func getAttachmentView(for attachment: Attachment) -> AttachmentView? {
|
||||
return attachmentViews.allObjects.first { $0.attachment.id == attachment.id }
|
||||
}
|
||||
|
||||
// MARK: - User Interaface
|
||||
|
||||
func updateUI(status: Status) {
|
||||
let attachments = status.attachments.filter { $0.kind == .image }
|
||||
|
||||
attachmentViews.removeAllObjects()
|
||||
subviews.forEach { $0.removeFromSuperview() }
|
||||
|
||||
if attachments.count > 0 {
|
||||
self.isHidden = false
|
||||
|
||||
let mainView: UIView
|
||||
switch attachments.count {
|
||||
case 1:
|
||||
mainView = createAttachmentView(attachments[0])
|
||||
makeMainView(createAttachmentView(attachments[0]))
|
||||
case 2:
|
||||
mainView = createAttachmentsStack(axis: .horizontal, arrangedSubviews: [
|
||||
createAttachmentView(attachments[0]),
|
||||
let left = createAttachmentView(attachments[0])
|
||||
makeMainView(createAttachmentsStack(axis: .horizontal, arrangedSubviews: [
|
||||
left,
|
||||
createAttachmentView(attachments[1])
|
||||
]))
|
||||
NSLayoutConstraint.activate([
|
||||
left.halfWidth()
|
||||
])
|
||||
case 3:
|
||||
mainView = createAttachmentsStack(axis: .horizontal, arrangedSubviews: [
|
||||
createAttachmentView(attachments[0]),
|
||||
let left = createAttachmentView(attachments[0])
|
||||
let topRight = createAttachmentView(attachments[1])
|
||||
makeMainView(createAttachmentsStack(axis: .horizontal, arrangedSubviews: [
|
||||
left,
|
||||
createAttachmentsStack(axis: .vertical, arrangedSubviews: [
|
||||
createAttachmentView(attachments[1]),
|
||||
topRight,
|
||||
createAttachmentView(attachments[2])
|
||||
])
|
||||
]))
|
||||
NSLayoutConstraint.activate([
|
||||
left.halfWidth(),
|
||||
topRight.halfHeight(),
|
||||
])
|
||||
case 4:
|
||||
mainView = createAttachmentsStack(axis: .horizontal, arrangedSubviews: [
|
||||
let topLeft = createAttachmentView(attachments[0])
|
||||
let left = createAttachmentsStack(axis: .vertical, arrangedSubviews: [
|
||||
topLeft,
|
||||
createAttachmentView(attachments[2])
|
||||
])
|
||||
let topRight = createAttachmentView(attachments[1])
|
||||
makeMainView(createAttachmentsStack(axis: .horizontal, arrangedSubviews: [
|
||||
left,
|
||||
createAttachmentsStack(axis: .vertical, arrangedSubviews: [
|
||||
createAttachmentView(attachments[0]),
|
||||
createAttachmentView(attachments[2])
|
||||
]),
|
||||
createAttachmentsStack(axis: .vertical, arrangedSubviews: [
|
||||
createAttachmentView(attachments[1]),
|
||||
topRight,
|
||||
createAttachmentView(attachments[3])
|
||||
])
|
||||
]))
|
||||
NSLayoutConstraint.activate([
|
||||
left.halfWidth(),
|
||||
topLeft.halfHeight(),
|
||||
topRight.halfHeight(),
|
||||
])
|
||||
default:
|
||||
fatalError("Too many attachments")
|
||||
}
|
||||
|
||||
addSubview(mainView)
|
||||
NSLayoutConstraint.activate([
|
||||
mainView.leadingAnchor.constraint(equalTo: leadingAnchor),
|
||||
mainView.trailingAnchor.constraint(equalTo: trailingAnchor),
|
||||
mainView.topAnchor.constraint(equalTo: topAnchor),
|
||||
mainView.bottomAnchor.constraint(equalTo: bottomAnchor)
|
||||
])
|
||||
} else {
|
||||
self.isHidden = true
|
||||
subviews.forEach { $0.removeFromSuperview() }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -74,15 +93,49 @@ class AttachmentsContainerView: UIView {
|
|||
let attachmentView = AttachmentView(attachment: attachment)
|
||||
attachmentView.delegate = delegate
|
||||
attachmentView.translatesAutoresizingMaskIntoConstraints = false
|
||||
attachmentViews.add(attachmentView)
|
||||
return attachmentView
|
||||
}
|
||||
|
||||
private func createAttachmentsStack(axis: NSLayoutConstraint.Axis, arrangedSubviews: [UIView]) -> UIStackView {
|
||||
let stack = UIStackView(arrangedSubviews: arrangedSubviews)
|
||||
stack.axis = axis
|
||||
stack.spacing = 8
|
||||
stack.spacing = 4
|
||||
stack.translatesAutoresizingMaskIntoConstraints = false
|
||||
return stack
|
||||
}
|
||||
|
||||
|
||||
private func makeMainView(_ view: UIView) {
|
||||
addSubview(view)
|
||||
NSLayoutConstraint.activate([
|
||||
view.leadingAnchor.constraint(equalTo: leadingAnchor),
|
||||
view.trailingAnchor.constraint(equalTo: trailingAnchor),
|
||||
view.topAnchor.constraint(equalTo: topAnchor),
|
||||
view.bottomAnchor.constraint(equalTo: bottomAnchor)
|
||||
])
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fileprivate extension UIView {
|
||||
enum RelativeSize {
|
||||
case full, half
|
||||
|
||||
var multiplier: CGFloat {
|
||||
switch self {
|
||||
case .full:
|
||||
return 1
|
||||
case .half:
|
||||
return 0.5
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func halfWidth(spacing: CGFloat = 4) -> NSLayoutConstraint {
|
||||
return widthAnchor.constraint(equalTo: superview!.widthAnchor, multiplier: 0.5, constant: -spacing / 2)
|
||||
}
|
||||
|
||||
func halfHeight(spacing: CGFloat = 4) -> NSLayoutConstraint {
|
||||
return heightAnchor.constraint(equalTo: superview!.heightAnchor, multiplier: 0.5, constant: -spacing / 2)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -307,9 +307,7 @@ extension StatusTableViewCell: AttachmentViewDelegate {
|
|||
func showLargeAttachment(for attachmentView: AttachmentView) {
|
||||
guard let status = MastodonCache.status(for: statusID) else { fatalError("Missing cached status \(statusID!)") }
|
||||
let startIndex = status.attachments.firstIndex { $0.id == attachmentView.attachment.id } ?? 0
|
||||
let sourceViews = status.attachments.map { attachment in
|
||||
attachmentsView.subviews.first { ($0 as! AttachmentView).attachment.id == attachment.id }!
|
||||
}
|
||||
let sourceViews = status.attachments.map(attachmentsView.getAttachmentView(for:))
|
||||
delegate?.showGallery(attachments: status.attachments, sourceViews: sourceViews, startIndex: startIndex)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue