Workaround for UIHostingConfiguration requiring iOS 16

This commit is contained in:
Shadowfacts 2024-12-16 23:46:46 -05:00
parent e0e9d4a185
commit 96fdef0558
2 changed files with 97 additions and 43 deletions

View File

@ -8,34 +8,7 @@
import UIKit import UIKit
import SwiftUI import SwiftUI
final class AttachmentCollectionViewCell: UICollectionViewCell { struct AttachmentCollectionViewCellView: View {
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 {
let attachment: DraftAttachment? let attachment: DraftAttachment?
var body: some View { var body: some View {
@ -147,6 +120,21 @@ private struct SquareFrame: Layout {
} }
} }
#if !os(visionOS)
@available(iOS, obsoleted: 16.0)
private struct LegacySquareFrame<Content: View>: 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 { private extension View {
@ViewBuilder @ViewBuilder
func squareFrame() -> some View { func squareFrame() -> some View {
@ -160,7 +148,9 @@ private extension View {
self self
} }
} else { } else {
self LegacySquareFrame {
self
}
} }
#endif #endif
} }

View File

@ -67,7 +67,7 @@ private class WrappedCollectionViewController: UIViewController {
var draft: Draft! var draft: Draft!
private var dataSource: UICollectionViewDiffableDataSource<Section, Item>! private var dataSource: UICollectionViewDiffableDataSource<Section, Item>!
fileprivate var currentInteractiveMoveStartOffsetInCell: CGPoint? fileprivate var currentInteractiveMoveStartOffsetInCell: CGPoint?
fileprivate var currentInteractiveMoveCell: AttachmentCollectionViewCell? fileprivate var currentInteractiveMoveCell: HostingCollectionViewCell?
fileprivate var addAttachment: ((DraftAttachment) -> Void)? = nil fileprivate var addAttachment: ((DraftAttachment) -> Void)? = nil
init(spacing: CGFloat, minItemSize: CGFloat) { init(spacing: CGFloat, minItemSize: CGFloat) {
@ -91,14 +91,11 @@ private class WrappedCollectionViewController: UIViewController {
section.interGroupSpacing = spacing section.interGroupSpacing = spacing
return section return section
} }
let attachmentCell = UICollectionView.CellRegistration<AttachmentCollectionViewCell, DraftAttachment> { cell, indexPath, attachment in let attachmentCell = UICollectionView.CellRegistration<HostingCollectionViewCell, DraftAttachment> { cell, indexPath, attachment in
cell.updateUI(attachment: attachment) cell.setView(AttachmentCollectionViewCellView(attachment: attachment))
} }
let addButtonCell = UICollectionView.CellRegistration<UICollectionViewCell, Bool> { [unowned self] cell, indexPath, item in let addButtonCell = UICollectionView.CellRegistration<HostingCollectionViewCell, Bool> { [unowned self] cell, indexPath, item in
cell.contentView.backgroundColor = .blue cell.setView(AddAttachmentButton(viewController: self, enabled: item))
// cell.contentConfiguration = UIHostingConfiguration(content: {
// AddAttachmentButton(viewController: self, enabled: item)
// }).margins(.all, .zero)
} }
let collectionView = IntrinsicContentSizeCollectionView(frame: .zero, collectionViewLayout: layout) let collectionView = IntrinsicContentSizeCollectionView(frame: .zero, collectionViewLayout: layout)
self.view = collectionView self.view = collectionView
@ -187,14 +184,14 @@ private class WrappedCollectionViewController: UIViewController {
case .ended: case .ended:
collectionView.endInteractiveMovement() collectionView.endInteractiveMovement()
UIView.animate(withDuration: 0.2) { UIView.animate(withDuration: 0.2) {
self.currentInteractiveMoveCell?.attachmentView.transform = .identity self.currentInteractiveMoveCell?.hostView?.transform = .identity
} }
currentInteractiveMoveCell = nil currentInteractiveMoveCell = nil
currentInteractiveMoveStartOffsetInCell = nil currentInteractiveMoveStartOffsetInCell = nil
case .cancelled: case .cancelled:
collectionView.cancelInteractiveMovement() collectionView.cancelInteractiveMovement()
UIView.animate(withDuration: 0.2) { UIView.animate(withDuration: 0.2) {
self.currentInteractiveMoveCell?.attachmentView.transform = .identity self.currentInteractiveMoveCell?.hostView?.transform = .identity
} }
currentInteractiveMoveCell = nil currentInteractiveMoveCell = nil
currentInteractiveMoveStartOffsetInCell = nil currentInteractiveMoveStartOffsetInCell = nil
@ -218,17 +215,20 @@ extension WrappedCollectionViewController: UIGestureRecognizerDelegate {
let collectionView = gestureRecognizer.view as! UICollectionView let collectionView = gestureRecognizer.view as! UICollectionView
let location = gestureRecognizer.location(in: collectionView) let location = gestureRecognizer.location(in: collectionView)
guard let indexPath = collectionView.indexPathForItem(at: location), 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 return false
} }
UIView.animate(withDuration: 0.2) { 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 currentInteractiveMoveCell = cell
currentInteractiveMoveStartOffsetInCell = gestureRecognizer.location(in: cell) currentInteractiveMoveStartOffsetInCell = gestureRecognizer.location(in: cell)
currentInteractiveMoveStartOffsetInCell!.x -= cell.bounds.midX currentInteractiveMoveStartOffsetInCell!.x -= cell.bounds.midX
currentInteractiveMoveStartOffsetInCell!.y -= cell.bounds.midY 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<V: View>(_ 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<AnyView>?
private(set) var hostView: UIView?
func setView<V: View>(_ 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 { private struct AddAttachmentButton: View {
unowned let viewController: WrappedCollectionViewController unowned let viewController: WrappedCollectionViewController
let enabled: Bool let enabled: Bool
@ -308,7 +364,7 @@ private struct AddAttachmentButton: View {
} }
} }
} label: { } label: {
Image(systemName: "photo.badge.plus") Image(systemName: iconName)
.imageScale(.large) .imageScale(.large)
.frame(maxWidth: .infinity, maxHeight: .infinity) .frame(maxWidth: .infinity, maxHeight: .infinity)
.background { .background {
@ -321,6 +377,14 @@ private struct AddAttachmentButton: View {
} }
.disabled(!enabled) .disabled(!enabled)
} }
private var iconName: String {
if #available(iOS 17.0, *) {
"photo.badge.plus"
} else {
"photo"
}
}
} }
struct AddAttachmentConditionsModifier: ViewModifier { struct AddAttachmentConditionsModifier: ViewModifier {