Use gallery VC for editing attachment descriptions
This commit is contained in:
parent
8cc9849b36
commit
ec9673f6c0
|
@ -22,13 +22,22 @@ let package = Package(
|
|||
.package(path: "../MatchedGeometryPresentation"),
|
||||
.package(path: "../TuskerPreferences"),
|
||||
.package(path: "../UserAccounts"),
|
||||
.package(path: "../GalleryVC"),
|
||||
],
|
||||
targets: [
|
||||
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
|
||||
// Targets can depend on other targets in this package, and on products in packages this package depends on.
|
||||
.target(
|
||||
name: "ComposeUI",
|
||||
dependencies: ["Pachyderm", "InstanceFeatures", "TuskerComponents", "MatchedGeometryPresentation", "TuskerPreferences", "UserAccounts"],
|
||||
dependencies: [
|
||||
"Pachyderm",
|
||||
"InstanceFeatures",
|
||||
"TuskerComponents",
|
||||
"MatchedGeometryPresentation",
|
||||
"TuskerPreferences",
|
||||
"UserAccounts",
|
||||
"GalleryVC",
|
||||
],
|
||||
swiftSettings: [
|
||||
.swiftLanguageMode(.v5)
|
||||
]),
|
||||
|
|
|
@ -76,7 +76,7 @@ private struct AttachmentRemoveButton: View {
|
|||
}
|
||||
|
||||
private struct AttachmentDescriptionLabel: View {
|
||||
let attachment: DraftAttachment
|
||||
@ObservedObject var attachment: DraftAttachment
|
||||
|
||||
var body: some View {
|
||||
ZStack(alignment: .bottomLeading) {
|
||||
|
@ -87,10 +87,8 @@ private struct AttachmentDescriptionLabel: View {
|
|||
)
|
||||
|
||||
label
|
||||
.lineLimit(1)
|
||||
.font(.callout)
|
||||
.foregroundStyle(.white)
|
||||
.shadow(radius: 1)
|
||||
.shadow(color: .black.opacity(0.5), radius: 1)
|
||||
.padding([.horizontal, .bottom], 4)
|
||||
}
|
||||
}
|
||||
|
@ -100,8 +98,12 @@ private struct AttachmentDescriptionLabel: View {
|
|||
if attachment.attachmentDescription.isEmpty {
|
||||
Label("Add alt", systemImage: "pencil")
|
||||
.labelStyle(NarrowSpacingLabelStyle())
|
||||
.font(.callout)
|
||||
.lineLimit(1)
|
||||
} else {
|
||||
Text(attachment.attachmentDescription)
|
||||
.font(.caption)
|
||||
.lineLimit(2)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
//
|
||||
// AttachmentGalleryDataSource.swift
|
||||
// ComposeUI
|
||||
//
|
||||
// Created by Shadowfacts on 11/21/24.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import GalleryVC
|
||||
|
||||
struct AttachmentsGalleryDataSource: GalleryDataSource {
|
||||
let collectionView: UICollectionView
|
||||
let attachmentAtIndex: (Int) -> DraftAttachment?
|
||||
|
||||
func galleryItemsCount() -> Int {
|
||||
collectionView.numberOfItems(inSection: 0) - 1
|
||||
}
|
||||
|
||||
func galleryContentViewController(forItemAt index: Int) -> any GalleryVC.GalleryContentViewController {
|
||||
let attachment = attachmentAtIndex(index)!
|
||||
|
||||
let content: any GalleryContentViewController
|
||||
switch attachment.data {
|
||||
case .editing(_, _, _):
|
||||
fatalError("TODO")
|
||||
|
||||
case .asset(_):
|
||||
fatalError("TODO")
|
||||
|
||||
case .drawing(let drawing):
|
||||
let image = drawing.imageInLightMode(from: drawing.bounds)
|
||||
content = ImageGalleryContentViewController(image: image, caption: nil, gifController: nil)
|
||||
|
||||
case .file(_, _):
|
||||
fatalError("TODO")
|
||||
|
||||
case .none:
|
||||
return LoadingGalleryContentViewController(caption: nil) {
|
||||
nil
|
||||
}
|
||||
}
|
||||
|
||||
return EditAttachmentWrapperGalleryContentViewController(draftAttachment: attachment, wrapped: content)
|
||||
}
|
||||
|
||||
func galleryContentTransitionSourceView(forItemAt index: Int) -> UIView? {
|
||||
collectionView.cellForItem(at: IndexPath(item: index, section: 0))
|
||||
}
|
||||
|
||||
}
|
|
@ -8,6 +8,7 @@
|
|||
import SwiftUI
|
||||
import PhotosUI
|
||||
import PencilKit
|
||||
import GalleryVC
|
||||
|
||||
struct AttachmentsSection: View {
|
||||
@ObservedObject var draft: Draft
|
||||
|
@ -24,14 +25,49 @@ struct AttachmentsSection: View {
|
|||
}
|
||||
}
|
||||
|
||||
private struct WrappedCollectionView: UIViewRepresentable {
|
||||
// Use a UIViewControllerRepresentable so we have something from which to present the gallery VC.
|
||||
private struct WrappedCollectionView: UIViewControllerRepresentable {
|
||||
@ObservedObject var draft: Draft
|
||||
let spacing: CGFloat
|
||||
let minItemSize: CGFloat
|
||||
|
||||
func makeUIView(context: Context) -> some UIView {
|
||||
let layout = UICollectionViewCompositionalLayout { section, environment in
|
||||
let (itemSize, itemsPerRow) = itemSize(width: environment.container.contentSize.width)
|
||||
func makeUIViewController(context: Context) -> WrappedCollectionViewController {
|
||||
WrappedCollectionViewController(spacing: spacing, minItemSize: minItemSize)
|
||||
}
|
||||
|
||||
func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {
|
||||
uiViewController.draft = draft
|
||||
uiViewController.addAttachment = {
|
||||
DraftsPersistentContainer.shared.viewContext.insert($0)
|
||||
$0.draft = draft
|
||||
draft.attachments.add($0)
|
||||
}
|
||||
uiViewController.updateAttachments()
|
||||
}
|
||||
}
|
||||
|
||||
private class WrappedCollectionViewController: UIViewController {
|
||||
let spacing: CGFloat
|
||||
let minItemSize: CGFloat
|
||||
var draft: Draft!
|
||||
private var dataSource: UICollectionViewDiffableDataSource<Section, Item>!
|
||||
fileprivate var currentInteractiveMoveStartOffsetInCell: CGPoint?
|
||||
fileprivate var currentInteractiveMoveCell: AttachmentCollectionViewCell?
|
||||
fileprivate var addAttachment: ((DraftAttachment) -> Void)? = nil
|
||||
|
||||
init(spacing: CGFloat, minItemSize: CGFloat) {
|
||||
self.spacing = spacing
|
||||
self.minItemSize = minItemSize
|
||||
super.init(nibName: nil, bundle: nil)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override func loadView() {
|
||||
let layout = UICollectionViewCompositionalLayout { [unowned self] section, environment in
|
||||
let (itemSize, itemsPerRow) = self.itemSize(width: environment.container.contentSize.width)
|
||||
|
||||
let item = NSCollectionLayoutItem(layoutSize: .init(widthDimension: .absolute(itemSize), heightDimension: .absolute(itemSize)))
|
||||
let group = NSCollectionLayoutGroup.horizontal(layoutSize: .init(widthDimension: .fractionalWidth(1), heightDimension: .absolute(itemSize)), repeatingSubitem: item, count: itemsPerRow)
|
||||
|
@ -40,9 +76,23 @@ private struct WrappedCollectionView: UIViewRepresentable {
|
|||
section.interGroupSpacing = spacing
|
||||
return section
|
||||
}
|
||||
let view = IntrinsicContentSizeCollectionView(frame: .zero, collectionViewLayout: layout)
|
||||
let dataSource = UICollectionViewDiffableDataSource<Section, Item>(collectionView: view) { collectionView, indexPath, itemIdentifier in
|
||||
context.coordinator.makeCell(collectionView: collectionView, indexPath: indexPath, item: itemIdentifier)
|
||||
let attachmentCell = UICollectionView.CellRegistration<AttachmentCollectionViewCell, DraftAttachment> { cell, indexPath, attachment in
|
||||
cell.updateUI(attachment: attachment)
|
||||
}
|
||||
let addButtonCell = UICollectionView.CellRegistration<UICollectionViewCell, Bool> { [unowned self] cell, indexPath, item in
|
||||
cell.contentConfiguration = UIHostingConfiguration(content: {
|
||||
AddAttachmentButton(viewController: self, enabled: item)
|
||||
}).margins(.all, .zero)
|
||||
}
|
||||
let collectionView = IntrinsicContentSizeCollectionView(frame: .zero, collectionViewLayout: layout)
|
||||
self.view = collectionView
|
||||
dataSource = UICollectionViewDiffableDataSource<Section, Item>(collectionView: collectionView) { collectionView, indexPath, itemIdentifier in
|
||||
switch itemIdentifier {
|
||||
case .attachment(let attachment):
|
||||
return collectionView.dequeueConfiguredReusableCell(using: attachmentCell, for: indexPath, item: attachment)
|
||||
case .addButton:
|
||||
return collectionView.dequeueConfiguredReusableCell(using: addButtonCell, for: indexPath, item: true)
|
||||
}
|
||||
}
|
||||
dataSource.reorderingHandlers.canReorderItem = { item in
|
||||
switch item {
|
||||
|
@ -52,7 +102,7 @@ private struct WrappedCollectionView: UIViewRepresentable {
|
|||
false
|
||||
}
|
||||
}
|
||||
dataSource.reorderingHandlers.didReorder = { transaction in
|
||||
dataSource.reorderingHandlers.didReorder = { [unowned self] transaction in
|
||||
let attachmentChanges = transaction.difference.map {
|
||||
switch $0 {
|
||||
case .insert(let offset, let element, let associatedWith):
|
||||
|
@ -67,35 +117,14 @@ private struct WrappedCollectionView: UIViewRepresentable {
|
|||
let array = draft.draftAttachments.applying(attachmentsDiff)!
|
||||
draft.attachments = NSMutableOrderedSet(array: array)
|
||||
}
|
||||
context.coordinator.dataSource = dataSource
|
||||
|
||||
view.isScrollEnabled = false
|
||||
view.clipsToBounds = false
|
||||
view.delegate = context.coordinator
|
||||
collectionView.isScrollEnabled = false
|
||||
collectionView.clipsToBounds = false
|
||||
collectionView.delegate = self
|
||||
|
||||
let longPressRecognizer = UILongPressGestureRecognizer(target: context.coordinator, action: #selector(WrappedCollectionViewCoordinator.reorderingLongPressRecognized))
|
||||
longPressRecognizer.delegate = context.coordinator
|
||||
view.addGestureRecognizer(longPressRecognizer)
|
||||
|
||||
return view
|
||||
}
|
||||
|
||||
func updateUIView(_ uiView: UIViewType, context: Context) {
|
||||
var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
|
||||
snapshot.appendSections([.all])
|
||||
snapshot.appendItems(draft.draftAttachments.map { .attachment($0) })
|
||||
snapshot.appendItems([.addButton])
|
||||
context.coordinator.dataSource.apply(snapshot)
|
||||
context.coordinator.addAttachment = {
|
||||
DraftsPersistentContainer.shared.viewContext.insert($0)
|
||||
$0.draft = draft
|
||||
draft.attachments.add($0)
|
||||
}
|
||||
context.coordinator.draft = draft
|
||||
}
|
||||
|
||||
func makeCoordinator() -> WrappedCollectionViewCoordinator {
|
||||
WrappedCollectionViewCoordinator()
|
||||
let longPressRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(reorderingLongPressRecognized))
|
||||
longPressRecognizer.delegate = self
|
||||
collectionView.addGestureRecognizer(longPressRecognizer)
|
||||
}
|
||||
|
||||
private func itemSize(width: CGFloat) -> (CGFloat, Int) {
|
||||
|
@ -119,41 +148,12 @@ private struct WrappedCollectionView: UIViewRepresentable {
|
|||
return (itemSize, Int(fittingCount))
|
||||
}
|
||||
|
||||
enum Section {
|
||||
case all
|
||||
}
|
||||
|
||||
enum Item: Hashable {
|
||||
case attachment(DraftAttachment)
|
||||
case addButton
|
||||
}
|
||||
}
|
||||
|
||||
private class WrappedCollectionViewCoordinator: NSObject {
|
||||
var draft: Draft!
|
||||
var dataSource: UICollectionViewDiffableDataSource<WrappedCollectionView.Section, WrappedCollectionView.Item>!
|
||||
var currentInteractiveMoveStartOffsetInCell: CGPoint?
|
||||
var currentInteractiveMoveCell: AttachmentCollectionViewCell?
|
||||
var addAttachment: ((DraftAttachment) -> Void)? = nil
|
||||
|
||||
private let attachmentCell = UICollectionView.CellRegistration<AttachmentCollectionViewCell, DraftAttachment> { cell, indexPath, attachment in
|
||||
cell.updateUI(attachment: attachment)
|
||||
}
|
||||
|
||||
private let addButtonCell = UICollectionView.CellRegistration<UICollectionViewCell, (WrappedCollectionViewCoordinator, Bool)> { cell, indexPath, item in
|
||||
let (coordinator, enabled) = item
|
||||
cell.contentConfiguration = UIHostingConfiguration(content: {
|
||||
AddAttachmentButton(coordinator: coordinator, enabled: enabled)
|
||||
}).margins(.all, .zero)
|
||||
}
|
||||
|
||||
func makeCell(collectionView: UICollectionView, indexPath: IndexPath, item: WrappedCollectionView.Item) -> UICollectionViewCell {
|
||||
switch item {
|
||||
case .attachment(let attachment):
|
||||
return collectionView.dequeueConfiguredReusableCell(using: attachmentCell, for: indexPath, item: attachment)
|
||||
case .addButton:
|
||||
return collectionView.dequeueConfiguredReusableCell(using: addButtonCell, for: indexPath, item: (self, true))
|
||||
}
|
||||
func updateAttachments() {
|
||||
var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
|
||||
snapshot.appendSections([.all])
|
||||
snapshot.appendItems(draft.draftAttachments.map { .attachment($0) })
|
||||
snapshot.appendItems([.addButton])
|
||||
dataSource.apply(snapshot)
|
||||
}
|
||||
|
||||
@objc func reorderingLongPressRecognized(_ recognizer: UILongPressGestureRecognizer) {
|
||||
|
@ -186,9 +186,18 @@ private class WrappedCollectionViewCoordinator: NSObject {
|
|||
break
|
||||
}
|
||||
}
|
||||
|
||||
enum Section {
|
||||
case all
|
||||
}
|
||||
|
||||
extension WrappedCollectionViewCoordinator: UIGestureRecognizerDelegate {
|
||||
enum Item: Hashable {
|
||||
case attachment(DraftAttachment)
|
||||
case addButton
|
||||
}
|
||||
}
|
||||
|
||||
extension WrappedCollectionViewController: UIGestureRecognizerDelegate {
|
||||
func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
|
||||
let collectionView = gestureRecognizer.view as! UICollectionView
|
||||
let location = gestureRecognizer.location(in: collectionView)
|
||||
|
@ -207,7 +216,7 @@ extension WrappedCollectionViewCoordinator: UIGestureRecognizerDelegate {
|
|||
}
|
||||
}
|
||||
|
||||
extension WrappedCollectionViewCoordinator: UICollectionViewDelegate {
|
||||
extension WrappedCollectionViewController: UICollectionViewDelegate {
|
||||
func collectionView(_ collectionView: UICollectionView, targetIndexPathForMoveOfItemFromOriginalIndexPath originalIndexPath: IndexPath, atCurrentIndexPath currentIndexPath: IndexPath, toProposedIndexPath proposedIndexPath: IndexPath) -> IndexPath {
|
||||
let snapshot = dataSource.snapshot()
|
||||
let items = snapshot.itemIdentifiers(inSection: .all).count
|
||||
|
@ -217,6 +226,24 @@ extension WrappedCollectionViewCoordinator: UICollectionViewDelegate {
|
|||
return proposedIndexPath
|
||||
}
|
||||
}
|
||||
|
||||
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
|
||||
guard case .attachment(_) = dataSource.itemIdentifier(for: indexPath) else {
|
||||
return
|
||||
}
|
||||
let dataSource = AttachmentsGalleryDataSource(collectionView: collectionView) { [dataSource] in
|
||||
let item = dataSource?.itemIdentifier(for: IndexPath(item: $0, section: 0))
|
||||
switch item {
|
||||
case .attachment(let attachment):
|
||||
return attachment
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
let galleryVC = GalleryViewController(dataSource: dataSource, initialItemIndex: indexPath.item)
|
||||
galleryVC.showShareButton = false
|
||||
present(galleryVC, animated: true)
|
||||
}
|
||||
}
|
||||
|
||||
private final class IntrinsicContentSizeCollectionView: UICollectionView {
|
||||
|
@ -237,7 +264,7 @@ private final class IntrinsicContentSizeCollectionView: UICollectionView {
|
|||
}
|
||||
|
||||
private struct AddAttachmentButton: View {
|
||||
let coordinator: WrappedCollectionViewCoordinator
|
||||
unowned let viewController: WrappedCollectionViewController
|
||||
let enabled: Bool
|
||||
@Environment(\.composeUIConfig.presentAssetPicker) private var presentAssetPicker
|
||||
@Environment(\.composeUIConfig.presentDrawing) private var presentDrawing
|
||||
|
@ -247,7 +274,7 @@ private struct AddAttachmentButton: View {
|
|||
if let presentAssetPicker {
|
||||
Button("Add photo or video", systemImage: "photo") {
|
||||
presentAssetPicker {
|
||||
let draft = coordinator.draft!
|
||||
let draft = viewController.draft!
|
||||
AttachmentsListSection.insertAttachments(in: draft, at: draft.attachments.count, itemProviders: $0.map(\.itemProvider))
|
||||
}
|
||||
}
|
||||
|
@ -255,7 +282,7 @@ private struct AddAttachmentButton: View {
|
|||
if let presentDrawing {
|
||||
Button("Draw something", systemImage: "hand.draw") {
|
||||
presentDrawing(PKDrawing()) { drawing in
|
||||
let draft = coordinator.draft!
|
||||
let draft = viewController.draft!
|
||||
let attachment = DraftAttachment(context: DraftsPersistentContainer.shared.viewContext)
|
||||
attachment.id = UUID()
|
||||
attachment.drawing = drawing
|
||||
|
|
|
@ -0,0 +1,198 @@
|
|||
//
|
||||
// EditAttachmentWrapperGalleryContentViewController.swift
|
||||
// ComposeUI
|
||||
//
|
||||
// Created by Shadowfacts on 11/22/24.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import GalleryVC
|
||||
|
||||
class EditAttachmentWrapperGalleryContentViewController: UIViewController, GalleryContentViewController {
|
||||
let draftAttachment: DraftAttachment
|
||||
let wrapped: any GalleryContentViewController
|
||||
|
||||
var container: (any GalleryContentViewControllerContainer)?
|
||||
|
||||
var contentSize: CGSize {
|
||||
wrapped.contentSize
|
||||
}
|
||||
|
||||
var activityItemsForSharing: [Any] {
|
||||
wrapped.activityItemsForSharing
|
||||
}
|
||||
|
||||
var caption: String? {
|
||||
wrapped.caption
|
||||
}
|
||||
|
||||
private lazy var editDescriptionViewController: EditAttachmentDescriptionViewController = EditAttachmentDescriptionViewController(draftAttachment: draftAttachment, wrapped: wrapped.bottomControlsAccessoryViewController)
|
||||
|
||||
var bottomControlsAccessoryViewController: UIViewController? {
|
||||
editDescriptionViewController
|
||||
}
|
||||
|
||||
var insetBottomControlsAccessoryViewControllerToSafeArea: Bool {
|
||||
false
|
||||
}
|
||||
|
||||
var canAnimateFromSourceView: Bool {
|
||||
wrapped.canAnimateFromSourceView
|
||||
}
|
||||
|
||||
var hideControlsOnZoom: Bool {
|
||||
false
|
||||
}
|
||||
|
||||
init(draftAttachment: DraftAttachment, wrapped: any GalleryContentViewController) {
|
||||
self.draftAttachment = draftAttachment
|
||||
self.wrapped = wrapped
|
||||
|
||||
super.init(nibName: nil, bundle: nil)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
wrapped.container = container
|
||||
addChild(wrapped)
|
||||
wrapped.view.translatesAutoresizingMaskIntoConstraints = false
|
||||
view.addSubview(wrapped.view)
|
||||
NSLayoutConstraint.activate([
|
||||
wrapped.view.leadingAnchor.constraint(equalTo: view.leadingAnchor),
|
||||
wrapped.view.trailingAnchor.constraint(equalTo: view.trailingAnchor),
|
||||
wrapped.view.topAnchor.constraint(equalTo: view.topAnchor),
|
||||
wrapped.view.bottomAnchor.constraint(equalTo: view.bottomAnchor),
|
||||
])
|
||||
wrapped.didMove(toParent: self)
|
||||
}
|
||||
|
||||
func setControlsVisible(_ visible: Bool, animated: Bool, dueToUserInteraction: Bool) {
|
||||
wrapped.setControlsVisible(visible, animated: animated, dueToUserInteraction: dueToUserInteraction)
|
||||
if !visible {
|
||||
editDescriptionViewController.textView?.resignFirstResponder()
|
||||
}
|
||||
}
|
||||
|
||||
func galleryContentDidAppear() {
|
||||
wrapped.galleryContentDidAppear()
|
||||
}
|
||||
|
||||
func galleryContentWillDisappear() {
|
||||
wrapped.galleryContentWillDisappear()
|
||||
}
|
||||
|
||||
func shouldHideControls() -> Bool {
|
||||
if editDescriptionViewController.textView.isFirstResponder {
|
||||
editDescriptionViewController.textView.resignFirstResponder()
|
||||
return false
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class EditAttachmentDescriptionViewController: UIViewController {
|
||||
private let draftAttachment: DraftAttachment
|
||||
private let wrapped: UIViewController?
|
||||
|
||||
private(set) var textView: UITextView!
|
||||
private var isShowingPlaceholder = false
|
||||
|
||||
private var descriptionObservation: NSKeyValueObservation?
|
||||
|
||||
init(draftAttachment: DraftAttachment, wrapped: UIViewController?) {
|
||||
self.draftAttachment = draftAttachment
|
||||
self.wrapped = wrapped
|
||||
|
||||
super.init(nibName: nil, bundle: nil)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
view.overrideUserInterfaceStyle = .dark
|
||||
view.backgroundColor = .secondarySystemFill
|
||||
|
||||
let stack = UIStackView()
|
||||
stack.axis = .vertical
|
||||
stack.distribution = .fill
|
||||
stack.spacing = 0
|
||||
stack.translatesAutoresizingMaskIntoConstraints = false
|
||||
view.addSubview(stack)
|
||||
NSLayoutConstraint.activate([
|
||||
stack.leadingAnchor.constraint(equalTo: view.leadingAnchor),
|
||||
stack.trailingAnchor.constraint(equalTo: view.trailingAnchor),
|
||||
stack.topAnchor.constraint(equalTo: view.topAnchor),
|
||||
stack.bottomAnchor.constraint(equalTo: view.keyboardLayoutGuide.topAnchor),
|
||||
])
|
||||
|
||||
if let wrapped {
|
||||
stack.addArrangedSubview(wrapped.view)
|
||||
}
|
||||
|
||||
textView = UITextView()
|
||||
textView.backgroundColor = nil
|
||||
textView.font = .preferredFont(forTextStyle: .body)
|
||||
textView.adjustsFontForContentSizeCategory = true
|
||||
if draftAttachment.attachmentDescription.isEmpty {
|
||||
showPlaceholder()
|
||||
} else {
|
||||
removePlaceholder()
|
||||
textView.text = draftAttachment.attachmentDescription
|
||||
}
|
||||
textView.delegate = self
|
||||
stack.addArrangedSubview(textView)
|
||||
textView.heightAnchor.constraint(equalToConstant: 150).isActive = true
|
||||
|
||||
descriptionObservation = draftAttachment.observe(\.attachmentDescription) { [unowned self] _, _ in
|
||||
let desc = self.draftAttachment.attachmentDescription
|
||||
if desc.isEmpty {
|
||||
if !isShowingPlaceholder {
|
||||
showPlaceholder()
|
||||
}
|
||||
} else {
|
||||
if isShowingPlaceholder {
|
||||
removePlaceholder()
|
||||
}
|
||||
self.textView.text = desc
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func showPlaceholder() {
|
||||
isShowingPlaceholder = true
|
||||
textView.text = "Describe for the visually impaired"
|
||||
textView.textColor = .secondaryLabel
|
||||
}
|
||||
|
||||
fileprivate func removePlaceholder() {
|
||||
isShowingPlaceholder = false
|
||||
textView.text = ""
|
||||
textView.textColor = .label
|
||||
}
|
||||
}
|
||||
|
||||
extension EditAttachmentDescriptionViewController: UITextViewDelegate {
|
||||
func textViewDidBeginEditing(_ textView: UITextView) {
|
||||
if isShowingPlaceholder {
|
||||
removePlaceholder()
|
||||
}
|
||||
}
|
||||
|
||||
func textViewDidEndEditing(_ textView: UITextView) {
|
||||
draftAttachment.attachmentDescription = textView.text
|
||||
|
||||
if textView.text.isEmpty {
|
||||
showPlaceholder()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -153,7 +153,6 @@ open class VideoGalleryContentViewController: UIViewController, GalleryContentVi
|
|||
}
|
||||
|
||||
open var activityItemsForSharing: [Any] {
|
||||
// [VideoActivityItemSource(asset: item.asset, url: url)]
|
||||
[]
|
||||
}
|
||||
|
||||
|
|
|
@ -15,8 +15,11 @@ public protocol GalleryContentViewController: UIViewController {
|
|||
var caption: String? { get }
|
||||
var contentOverlayAccessoryViewController: UIViewController? { get }
|
||||
var bottomControlsAccessoryViewController: UIViewController? { get }
|
||||
var insetBottomControlsAccessoryViewControllerToSafeArea: Bool { get }
|
||||
var canAnimateFromSourceView: Bool { get }
|
||||
var hideControlsOnZoom: Bool { get }
|
||||
|
||||
func shouldHideControls() -> Bool
|
||||
func setControlsVisible(_ visible: Bool, animated: Bool, dueToUserInteraction: Bool)
|
||||
func galleryContentDidAppear()
|
||||
func galleryContentWillDisappear()
|
||||
|
@ -31,10 +34,22 @@ public extension GalleryContentViewController {
|
|||
nil
|
||||
}
|
||||
|
||||
var insetBottomControlsAccessoryViewControllerToSafeArea: Bool {
|
||||
true
|
||||
}
|
||||
|
||||
var canAnimateFromSourceView: Bool {
|
||||
true
|
||||
}
|
||||
|
||||
var hideControlsOnZoom: Bool {
|
||||
true
|
||||
}
|
||||
|
||||
func shouldHideControls() -> Bool {
|
||||
true
|
||||
}
|
||||
|
||||
func setControlsVisible(_ visible: Bool, animated: Bool, dueToUserInteraction: Bool) {
|
||||
}
|
||||
|
||||
|
|
|
@ -45,6 +45,12 @@ class GalleryItemViewController: UIViewController {
|
|||
|
||||
private var scrollViewSizeForLastZoomScaleUpdate: CGSize?
|
||||
|
||||
var showShareButton: Bool = true {
|
||||
didSet {
|
||||
shareButton?.isHidden = !showShareButton
|
||||
}
|
||||
}
|
||||
|
||||
override var prefersHomeIndicatorAutoHidden: Bool {
|
||||
return !controlsVisible
|
||||
}
|
||||
|
@ -66,6 +72,10 @@ class GalleryItemViewController: UIViewController {
|
|||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
// Need to use the keyboard layout guide in some way in this VC,
|
||||
// otherwise the keyboardLayoutGuide inside the bottom controls accessory view doesn't animate
|
||||
_ = view.keyboardLayoutGuide
|
||||
|
||||
scrollView = UIScrollView()
|
||||
scrollView.translatesAutoresizingMaskIntoConstraints = false
|
||||
scrollView.delegate = self
|
||||
|
@ -105,6 +115,7 @@ class GalleryItemViewController: UIViewController {
|
|||
return UIPointerStyle(effect: .highlight(effect.preview), shape: .roundedRect(button.frame))
|
||||
}
|
||||
shareButton.preferredBehavioralStyle = .pad
|
||||
shareButton.isHidden = !showShareButton
|
||||
shareButton.translatesAutoresizingMaskIntoConstraints = false
|
||||
updateShareButton()
|
||||
topControlsView.addSubview(shareButton)
|
||||
|
@ -137,6 +148,7 @@ class GalleryItemViewController: UIViewController {
|
|||
bottomControlsView.addArrangedSubview(controlsAccessory.view)
|
||||
controlsAccessory.didMove(toParent: self)
|
||||
|
||||
if content.insetBottomControlsAccessoryViewControllerToSafeArea {
|
||||
// Make sure the controls accessory is within the safe area.
|
||||
let spacer = UIView()
|
||||
bottomControlsView.addArrangedSubview(spacer)
|
||||
|
@ -144,6 +156,7 @@ class GalleryItemViewController: UIViewController {
|
|||
spacerTopConstraint.priority = .init(999)
|
||||
spacerTopConstraint.isActive = true
|
||||
}
|
||||
}
|
||||
|
||||
captionTextView = UITextView()
|
||||
captionTextView.backgroundColor = .clear
|
||||
|
@ -206,6 +219,14 @@ class GalleryItemViewController: UIViewController {
|
|||
singleTap.require(toFail: doubleTap)
|
||||
view.addGestureRecognizer(singleTap)
|
||||
view.addGestureRecognizer(doubleTap)
|
||||
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillUpdate), name: UIResponder.keyboardWillShowNotification, 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)
|
||||
}
|
||||
|
||||
@objc private func keyboardWillUpdate() {
|
||||
updateZoomScale(resetZoom: true)
|
||||
}
|
||||
|
||||
override func viewSafeAreaInsetsDidChange() {
|
||||
|
@ -328,7 +349,7 @@ class GalleryItemViewController: UIViewController {
|
|||
return
|
||||
}
|
||||
|
||||
let heightScale = view.bounds.height / content.contentSize.height
|
||||
let heightScale = (view.bounds.height - view.keyboardLayoutGuide.layoutFrame.height) / content.contentSize.height
|
||||
let widthScale = view.bounds.width / content.contentSize.width
|
||||
let minScale = min(widthScale, heightScale)
|
||||
let maxScale = minScale >= 1 ? minScale + 2 : 2
|
||||
|
@ -351,7 +372,7 @@ class GalleryItemViewController: UIViewController {
|
|||
|
||||
// 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.
|
||||
let yOffset = max(0, (view.bounds.height - content.view.frame.height) / 2)
|
||||
let yOffset = max(0, (view.bounds.height - view.keyboardLayoutGuide.layoutFrame.height - content.view.frame.height) / 2)
|
||||
contentViewTopConstraint!.constant = yOffset
|
||||
|
||||
let xOffset = max(0, (view.bounds.width - content.view.frame.width) / 2)
|
||||
|
@ -428,9 +449,11 @@ class GalleryItemViewController: UIViewController {
|
|||
scrollView.zoomScale > scrollView.minimumZoomScale {
|
||||
animateZoomOut()
|
||||
} else {
|
||||
if content.shouldHideControls() {
|
||||
setControlsVisible(!controlsVisible, animated: true, dueToUserInteraction: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc private func viewDoublePressed(_ recognizer: UITapGestureRecognizer) {
|
||||
guard scrollAndZoomEnabled else {
|
||||
|
@ -547,8 +570,10 @@ extension GalleryItemViewController: UIScrollViewDelegate {
|
|||
if scrollView.zoomScale <= scrollView.minimumZoomScale {
|
||||
setControlsVisible(true, animated: true, dueToUserInteraction: true)
|
||||
} else {
|
||||
if content.hideControlsOnZoom {
|
||||
setControlsVisible(false, animated: true, dueToUserInteraction: true)
|
||||
}
|
||||
}
|
||||
|
||||
centerContent()
|
||||
scrollView.layoutIfNeeded()
|
||||
|
|
|
@ -26,6 +26,14 @@ public class GalleryViewController: UIPageViewController {
|
|||
private var dismissInteraction: GalleryDismissInteraction!
|
||||
private var presentationAnimationCompletionHandlers: [() -> Void] = []
|
||||
|
||||
public var showShareButton: Bool = true {
|
||||
didSet {
|
||||
if viewControllers?.isEmpty == false {
|
||||
currentItemViewController.showShareButton = showShareButton
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override public var prefersStatusBarHidden: Bool {
|
||||
true
|
||||
}
|
||||
|
@ -89,7 +97,9 @@ public class GalleryViewController: UIPageViewController {
|
|||
|
||||
private func makeItemVC(index: Int) -> GalleryItemViewController {
|
||||
let content = galleryDataSource.galleryContentViewController(forItemAt: index)
|
||||
return GalleryItemViewController(delegate: self, itemIndex: index, content: content)
|
||||
let itemVC = GalleryItemViewController(delegate: self, itemIndex: index, content: content)
|
||||
itemVC.showShareButton = showShareButton
|
||||
return itemVC
|
||||
}
|
||||
|
||||
func presentationAnimationCompleted() {
|
||||
|
|
Loading…
Reference in New Issue