From 96fdef05589ec71c053a363272f7d4faaf10c6cc Mon Sep 17 00:00:00 2001 From: Shadowfacts Date: Mon, 16 Dec 2024 23:46:46 -0500 Subject: [PATCH] Workaround for UIHostingConfiguration requiring iOS 16 --- .../AttachmentCollectionViewCell.swift | 48 ++++------ .../Attachments/AttachmentsSection.swift | 92 ++++++++++++++++--- 2 files changed, 97 insertions(+), 43 deletions(-) diff --git a/Packages/ComposeUI/Sources/ComposeUI/Views/Attachments/AttachmentCollectionViewCell.swift b/Packages/ComposeUI/Sources/ComposeUI/Views/Attachments/AttachmentCollectionViewCell.swift index 42edc810..ec044c21 100644 --- a/Packages/ComposeUI/Sources/ComposeUI/Views/Attachments/AttachmentCollectionViewCell.swift +++ b/Packages/ComposeUI/Sources/ComposeUI/Views/Attachments/AttachmentCollectionViewCell.swift @@ -8,34 +8,7 @@ import UIKit import SwiftUI -final class AttachmentCollectionViewCell: UICollectionViewCell { - let attachmentView: UIView// & UIContentView - - override init(frame: CGRect) { - attachmentView = UIView() - attachmentView.backgroundColor = .red -// attachmentView = UIHostingConfiguration(content: { -// AttachmentCollectionViewCellView(attachment: nil) -// }).makeContentView() - - super.init(frame: frame) - - attachmentView.frame = bounds - addSubview(attachmentView) - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - func updateUI(attachment: DraftAttachment) { -// attachmentView.configuration = UIHostingConfiguration(content: { -// AttachmentCollectionViewCellView(attachment: attachment) -// }).margins(.all, .zero) - } -} - -private struct AttachmentCollectionViewCellView: View { +struct AttachmentCollectionViewCellView: View { let attachment: DraftAttachment? var body: some View { @@ -147,6 +120,21 @@ private struct SquareFrame: Layout { } } +#if !os(visionOS) +@available(iOS, obsoleted: 16.0) +private struct LegacySquareFrame: View { + @ViewBuilder let content: Content + + var body: some View { + GeometryReader { proxy in + let minDimension = min(proxy.size.width, proxy.size.height) + content + .frame(width: minDimension, height: minDimension, alignment: .center) + } + } +} +#endif + private extension View { @ViewBuilder func squareFrame() -> some View { @@ -160,7 +148,9 @@ private extension View { self } } else { - self + LegacySquareFrame { + self + } } #endif } diff --git a/Packages/ComposeUI/Sources/ComposeUI/Views/Attachments/AttachmentsSection.swift b/Packages/ComposeUI/Sources/ComposeUI/Views/Attachments/AttachmentsSection.swift index d550965f..2ffee08a 100644 --- a/Packages/ComposeUI/Sources/ComposeUI/Views/Attachments/AttachmentsSection.swift +++ b/Packages/ComposeUI/Sources/ComposeUI/Views/Attachments/AttachmentsSection.swift @@ -67,7 +67,7 @@ private class WrappedCollectionViewController: UIViewController { var draft: Draft! private var dataSource: UICollectionViewDiffableDataSource! fileprivate var currentInteractiveMoveStartOffsetInCell: CGPoint? - fileprivate var currentInteractiveMoveCell: AttachmentCollectionViewCell? + fileprivate var currentInteractiveMoveCell: HostingCollectionViewCell? fileprivate var addAttachment: ((DraftAttachment) -> Void)? = nil init(spacing: CGFloat, minItemSize: CGFloat) { @@ -91,14 +91,11 @@ private class WrappedCollectionViewController: UIViewController { section.interGroupSpacing = spacing return section } - let attachmentCell = UICollectionView.CellRegistration { cell, indexPath, attachment in - cell.updateUI(attachment: attachment) + let attachmentCell = UICollectionView.CellRegistration { cell, indexPath, attachment in + cell.setView(AttachmentCollectionViewCellView(attachment: attachment)) } - let addButtonCell = UICollectionView.CellRegistration { [unowned self] cell, indexPath, item in - cell.contentView.backgroundColor = .blue -// cell.contentConfiguration = UIHostingConfiguration(content: { -// AddAttachmentButton(viewController: self, enabled: item) -// }).margins(.all, .zero) + let addButtonCell = UICollectionView.CellRegistration { [unowned self] cell, indexPath, item in + cell.setView(AddAttachmentButton(viewController: self, enabled: item)) } let collectionView = IntrinsicContentSizeCollectionView(frame: .zero, collectionViewLayout: layout) self.view = collectionView @@ -187,14 +184,14 @@ private class WrappedCollectionViewController: UIViewController { case .ended: collectionView.endInteractiveMovement() UIView.animate(withDuration: 0.2) { - self.currentInteractiveMoveCell?.attachmentView.transform = .identity + self.currentInteractiveMoveCell?.hostView?.transform = .identity } currentInteractiveMoveCell = nil currentInteractiveMoveStartOffsetInCell = nil case .cancelled: collectionView.cancelInteractiveMovement() UIView.animate(withDuration: 0.2) { - self.currentInteractiveMoveCell?.attachmentView.transform = .identity + self.currentInteractiveMoveCell?.hostView?.transform = .identity } currentInteractiveMoveCell = nil currentInteractiveMoveStartOffsetInCell = nil @@ -218,17 +215,20 @@ extension WrappedCollectionViewController: UIGestureRecognizerDelegate { let collectionView = gestureRecognizer.view as! UICollectionView let location = gestureRecognizer.location(in: collectionView) guard let indexPath = collectionView.indexPathForItem(at: location), - let cell = collectionView.cellForItem(at: indexPath) as? AttachmentCollectionViewCell else { + let cell = collectionView.cellForItem(at: indexPath) as? HostingCollectionViewCell else { + return false + } + guard collectionView.beginInteractiveMovementForItem(at: indexPath) else { return false } UIView.animate(withDuration: 0.2) { - cell.attachmentView.transform = CGAffineTransform(scaleX: 1.2, y: 1.2) + cell.hostView?.transform = CGAffineTransform(scaleX: 1.2, y: 1.2) } currentInteractiveMoveCell = cell currentInteractiveMoveStartOffsetInCell = gestureRecognizer.location(in: cell) currentInteractiveMoveStartOffsetInCell!.x -= cell.bounds.midX currentInteractiveMoveStartOffsetInCell!.y -= cell.bounds.midY - return collectionView.beginInteractiveMovementForItem(at: indexPath) + return true } } @@ -279,6 +279,62 @@ private final class IntrinsicContentSizeCollectionView: UICollectionView { } } +#if os(visionOS) +private class HostingCollectionViewCell: UICollectionViewCell { + private(set) var hostView: UIView? + + func setView(_ view: V) { + let config = UIHostingConfiguration(content: { + view + }).margins(.all, 0) + + if let hostView = hostView as? UIContentView { + hostView.configuration = config + } else { + hostView = config.makeContentView() + hostView!.frame = contentView.bounds + contentView.addSubview(hostView!) + } + } +} +#else +@available(iOS, obsoleted: 16.0) +private class HostingCollectionViewCell: UICollectionViewCell { + @available(iOS, obsoleted: 16.0) + private var hostController: UIHostingController? + private(set) var hostView: UIView? + + func setView(_ view: V) { + if #available(iOS 16.0, *) { + let config = UIHostingConfiguration(content: { + view + }).margins(.all, 0) + + // We don't just use the cell's contentConfiguration property because we need to animate + // the size of the host view, and when the host view is the contentView, that doesn't work. + if let hostView = hostView as? UIContentView { + hostView.configuration = config + } else { + hostView = config.makeContentView() + hostView!.frame = contentView.bounds + contentView.addSubview(hostView!) + } + } else { + if let hostController { + hostController.rootView = AnyView(view) + } else { + let host = UIHostingController(rootView: AnyView(view)) + host.view.frame = contentView.bounds + contentView.addSubview(host.view) + hostController = host + hostView = host.view + // drop the hosting controller on the floor and hope nothing bad happens + } + } + } +} +#endif + private struct AddAttachmentButton: View { unowned let viewController: WrappedCollectionViewController let enabled: Bool @@ -308,7 +364,7 @@ private struct AddAttachmentButton: View { } } } label: { - Image(systemName: "photo.badge.plus") + Image(systemName: iconName) .imageScale(.large) .frame(maxWidth: .infinity, maxHeight: .infinity) .background { @@ -321,6 +377,14 @@ private struct AddAttachmentButton: View { } .disabled(!enabled) } + + private var iconName: String { + if #available(iOS 17.0, *) { + "photo.badge.plus" + } else { + "photo" + } + } } struct AddAttachmentConditionsModifier: ViewModifier {