Fix multi-attachment layout issues

This commit is contained in:
Shadowfacts 2019-06-16 22:39:46 -04:00
parent 55fc032f36
commit 85ed53b990
Signed by: shadowfacts
GPG Key ID: 94A5AB95422746E5
10 changed files with 124 additions and 56 deletions

View File

@ -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" {

View File

@ -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

View File

@ -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)

View File

@ -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]

View File

@ -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

View File

@ -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)

View File

@ -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()

View File

@ -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)
}

View File

@ -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)
}
}

View File

@ -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)
}
}