mirror of
https://github.com/metabolist/metatext.git
synced 2024-11-25 09:41:00 +00:00
Image viewing
This commit is contained in:
parent
f4cd293ec2
commit
9bb7ae0609
8 changed files with 302 additions and 3 deletions
|
@ -19,6 +19,9 @@
|
||||||
D0625E5D250F0B5C00502611 /* StatusContentConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0625E5C250F0B5C00502611 /* StatusContentConfiguration.swift */; };
|
D0625E5D250F0B5C00502611 /* StatusContentConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0625E5C250F0B5C00502611 /* StatusContentConfiguration.swift */; };
|
||||||
D06B492324D4611300642749 /* KingfisherSwiftUI in Frameworks */ = {isa = PBXBuildFile; productRef = D06B492224D4611300642749 /* KingfisherSwiftUI */; };
|
D06B492324D4611300642749 /* KingfisherSwiftUI in Frameworks */ = {isa = PBXBuildFile; productRef = D06B492224D4611300642749 /* KingfisherSwiftUI */; };
|
||||||
D06BC5E625202AD90079541D /* ProfileViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D06BC5E525202AD90079541D /* ProfileViewController.swift */; };
|
D06BC5E625202AD90079541D /* ProfileViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D06BC5E525202AD90079541D /* ProfileViewController.swift */; };
|
||||||
|
D08B8D3D253F929E00B1EBEF /* ImageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08B8D3C253F929E00B1EBEF /* ImageViewController.swift */; };
|
||||||
|
D08B8D42253F92B600B1EBEF /* ImagePageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08B8D41253F92B600B1EBEF /* ImagePageViewController.swift */; };
|
||||||
|
D08B8D4A253FC36500B1EBEF /* ImageNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08B8D49253FC36500B1EBEF /* ImageNavigationController.swift */; };
|
||||||
D0A1F4F7252E7D4B004435BF /* TableViewDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A1F4F6252E7D4B004435BF /* TableViewDataSource.swift */; };
|
D0A1F4F7252E7D4B004435BF /* TableViewDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A1F4F6252E7D4B004435BF /* TableViewDataSource.swift */; };
|
||||||
D0A3C2F725390A9700739F88 /* AppPreferences+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A3C2F625390A9700739F88 /* AppPreferences+Extensions.swift */; };
|
D0A3C2F725390A9700739F88 /* AppPreferences+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A3C2F625390A9700739F88 /* AppPreferences+Extensions.swift */; };
|
||||||
D0B32F50250B373600311912 /* RegistrationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B32F4F250B373600311912 /* RegistrationView.swift */; };
|
D0B32F50250B373600311912 /* RegistrationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B32F4F250B373600311912 /* RegistrationView.swift */; };
|
||||||
|
@ -116,6 +119,9 @@
|
||||||
D0666A2524C677B400F3F04B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
D0666A2524C677B400F3F04B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||||
D06BC5E525202AD90079541D /* ProfileViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileViewController.swift; sourceTree = "<group>"; };
|
D06BC5E525202AD90079541D /* ProfileViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileViewController.swift; sourceTree = "<group>"; };
|
||||||
D085C3BB25008DEC008A6C5E /* DB */ = {isa = PBXFileReference; lastKnownFileType = folder; path = DB; sourceTree = "<group>"; };
|
D085C3BB25008DEC008A6C5E /* DB */ = {isa = PBXFileReference; lastKnownFileType = folder; path = DB; sourceTree = "<group>"; };
|
||||||
|
D08B8D3C253F929E00B1EBEF /* ImageViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageViewController.swift; sourceTree = "<group>"; };
|
||||||
|
D08B8D41253F92B600B1EBEF /* ImagePageViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImagePageViewController.swift; sourceTree = "<group>"; };
|
||||||
|
D08B8D49253FC36500B1EBEF /* ImageNavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageNavigationController.swift; sourceTree = "<group>"; };
|
||||||
D0A1F4F6252E7D4B004435BF /* TableViewDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableViewDataSource.swift; sourceTree = "<group>"; };
|
D0A1F4F6252E7D4B004435BF /* TableViewDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableViewDataSource.swift; sourceTree = "<group>"; };
|
||||||
D0A3C2F625390A9700739F88 /* AppPreferences+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AppPreferences+Extensions.swift"; sourceTree = "<group>"; };
|
D0A3C2F625390A9700739F88 /* AppPreferences+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AppPreferences+Extensions.swift"; sourceTree = "<group>"; };
|
||||||
D0AD03552505814D0085A466 /* Base16 */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Base16; sourceTree = "<group>"; };
|
D0AD03552505814D0085A466 /* Base16 */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Base16; sourceTree = "<group>"; };
|
||||||
|
@ -297,7 +303,6 @@
|
||||||
D0F0B125251A90F400942152 /* AccountListCell.swift */,
|
D0F0B125251A90F400942152 /* AccountListCell.swift */,
|
||||||
D0F0B10D251A868200942152 /* AccountView.swift */,
|
D0F0B10D251A868200942152 /* AccountView.swift */,
|
||||||
D0C7D42424F76169001EBDBB /* AddIdentityView.swift */,
|
D0C7D42424F76169001EBDBB /* AddIdentityView.swift */,
|
||||||
D03B1B29253818F3008F964B /* MediaPreferencesView.swift */,
|
|
||||||
D0C7D42324F76169001EBDBB /* CustomEmojiText.swift */,
|
D0C7D42324F76169001EBDBB /* CustomEmojiText.swift */,
|
||||||
D0BEB21024FA2A90001B0F04 /* EditFilterView.swift */,
|
D0BEB21024FA2A90001B0F04 /* EditFilterView.swift */,
|
||||||
D0BEB20424FA1107001B0F04 /* FiltersView.swift */,
|
D0BEB20424FA1107001B0F04 /* FiltersView.swift */,
|
||||||
|
@ -307,6 +312,7 @@
|
||||||
D0B8510B25259E56004E0744 /* LoadMoreCell.swift */,
|
D0B8510B25259E56004E0744 /* LoadMoreCell.swift */,
|
||||||
D0E569DF252931B100FA1D72 /* LoadMoreContentConfiguration.swift */,
|
D0E569DF252931B100FA1D72 /* LoadMoreContentConfiguration.swift */,
|
||||||
D0E569DA2529319100FA1D72 /* LoadMoreView.swift */,
|
D0E569DA2529319100FA1D72 /* LoadMoreView.swift */,
|
||||||
|
D03B1B29253818F3008F964B /* MediaPreferencesView.swift */,
|
||||||
D0C7D42D24F76169001EBDBB /* NotificationTypesPreferencesView.swift */,
|
D0C7D42D24F76169001EBDBB /* NotificationTypesPreferencesView.swift */,
|
||||||
D0FE1C8E253686F9003EF1EB /* PlayerView.swift */,
|
D0FE1C8E253686F9003EF1EB /* PlayerView.swift */,
|
||||||
D0C7D42824F76169001EBDBB /* PostingReadingPreferencesView.swift */,
|
D0C7D42824F76169001EBDBB /* PostingReadingPreferencesView.swift */,
|
||||||
|
@ -328,6 +334,9 @@
|
||||||
D0C7D43024F76169001EBDBB /* View Controllers */ = {
|
D0C7D43024F76169001EBDBB /* View Controllers */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
D08B8D41253F92B600B1EBEF /* ImagePageViewController.swift */,
|
||||||
|
D08B8D49253FC36500B1EBEF /* ImageNavigationController.swift */,
|
||||||
|
D08B8D3C253F929E00B1EBEF /* ImageViewController.swift */,
|
||||||
D06BC5E525202AD90079541D /* ProfileViewController.swift */,
|
D06BC5E525202AD90079541D /* ProfileViewController.swift */,
|
||||||
D0F0B12D251A97E400942152 /* TableViewController.swift */,
|
D0F0B12D251A97E400942152 /* TableViewController.swift */,
|
||||||
);
|
);
|
||||||
|
@ -569,6 +578,7 @@
|
||||||
D0F0B12E251A97E400942152 /* TableViewController.swift in Sources */,
|
D0F0B12E251A97E400942152 /* TableViewController.swift in Sources */,
|
||||||
D0FE1C8F253686F9003EF1EB /* PlayerView.swift in Sources */,
|
D0FE1C8F253686F9003EF1EB /* PlayerView.swift in Sources */,
|
||||||
D0F0B113251A86A000942152 /* AccountContentConfiguration.swift in Sources */,
|
D0F0B113251A86A000942152 /* AccountContentConfiguration.swift in Sources */,
|
||||||
|
D08B8D42253F92B600B1EBEF /* ImagePageViewController.swift in Sources */,
|
||||||
D01F41D924F880C400D55A2D /* TouchFallthroughTextView.swift in Sources */,
|
D01F41D924F880C400D55A2D /* TouchFallthroughTextView.swift in Sources */,
|
||||||
D0C7D4D624F7616A001EBDBB /* NSMutableAttributedString+Extensions.swift in Sources */,
|
D0C7D4D624F7616A001EBDBB /* NSMutableAttributedString+Extensions.swift in Sources */,
|
||||||
D0625E59250F092900502611 /* StatusListCell.swift in Sources */,
|
D0625E59250F092900502611 /* StatusListCell.swift in Sources */,
|
||||||
|
@ -591,9 +601,11 @@
|
||||||
D01EF22425182B1F00650C6B /* AccountHeaderView.swift in Sources */,
|
D01EF22425182B1F00650C6B /* AccountHeaderView.swift in Sources */,
|
||||||
D0EA59482522B8B600804347 /* ViewConstants.swift in Sources */,
|
D0EA59482522B8B600804347 /* ViewConstants.swift in Sources */,
|
||||||
D0C7D49824F7616A001EBDBB /* CustomEmojiText.swift in Sources */,
|
D0C7D49824F7616A001EBDBB /* CustomEmojiText.swift in Sources */,
|
||||||
|
D08B8D3D253F929E00B1EBEF /* ImageViewController.swift in Sources */,
|
||||||
D0B8510C25259E56004E0744 /* LoadMoreCell.swift in Sources */,
|
D0B8510C25259E56004E0744 /* LoadMoreCell.swift in Sources */,
|
||||||
D01F41E424F8889700D55A2D /* StatusAttachmentsView.swift in Sources */,
|
D01F41E424F8889700D55A2D /* StatusAttachmentsView.swift in Sources */,
|
||||||
D0BEB21124FA2A91001B0F04 /* EditFilterView.swift in Sources */,
|
D0BEB21124FA2A91001B0F04 /* EditFilterView.swift in Sources */,
|
||||||
|
D08B8D4A253FC36500B1EBEF /* ImageNavigationController.swift in Sources */,
|
||||||
D0030982250C6C8500EACB32 /* URL+Extensions.swift in Sources */,
|
D0030982250C6C8500EACB32 /* URL+Extensions.swift in Sources */,
|
||||||
D00CB2ED2533ACC00080096B /* StatusView.swift in Sources */,
|
D00CB2ED2533ACC00080096B /* StatusView.swift in Sources */,
|
||||||
D0A1F4F7252E7D4B004435BF /* TableViewDataSource.swift in Sources */,
|
D0A1F4F7252E7D4B004435BF /* TableViewDataSource.swift in Sources */,
|
||||||
|
|
25
View Controllers/ImageNavigationController.swift
Normal file
25
View Controllers/ImageNavigationController.swift
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
// Copyright © 2020 Metabolist. All rights reserved.
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
class ImageNavigationController: UINavigationController {
|
||||||
|
private let imagePageViewController: ImagePageViewController
|
||||||
|
|
||||||
|
init(imagePageViewController: ImagePageViewController) {
|
||||||
|
self.imagePageViewController = imagePageViewController
|
||||||
|
|
||||||
|
super.init(rootViewController: imagePageViewController)
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(*, unavailable)
|
||||||
|
required init?(coder aDecoder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override func viewDidLoad() {
|
||||||
|
super.viewDidLoad()
|
||||||
|
|
||||||
|
hidesBarsOnTap = true
|
||||||
|
modalPresentationStyle = .fullScreen
|
||||||
|
}
|
||||||
|
}
|
76
View Controllers/ImagePageViewController.swift
Normal file
76
View Controllers/ImagePageViewController.swift
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
// Copyright © 2020 Metabolist. All rights reserved.
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import ViewModels
|
||||||
|
|
||||||
|
class ImagePageViewController: UIPageViewController {
|
||||||
|
let imageViewControllers: [ImageViewController]
|
||||||
|
|
||||||
|
init(initiallyVisible: AttachmentViewModel, statusViewModel: StatusViewModel) {
|
||||||
|
imageViewControllers = statusViewModel.attachmentViewModels.map(ImageViewController.init(viewModel:))
|
||||||
|
|
||||||
|
super.init(
|
||||||
|
transitionStyle: .scroll,
|
||||||
|
navigationOrientation: .horizontal,
|
||||||
|
options: [.interPageSpacing: CGFloat.defaultSpacing])
|
||||||
|
|
||||||
|
let index = statusViewModel.attachmentViewModels.firstIndex {
|
||||||
|
$0.attachment.id == initiallyVisible.attachment.id
|
||||||
|
}
|
||||||
|
|
||||||
|
setViewControllers([imageViewControllers[index ?? 0]], direction: .forward, animated: false)
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(*, unavailable)
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override func viewDidLoad() {
|
||||||
|
super.viewDidLoad()
|
||||||
|
|
||||||
|
dataSource = self
|
||||||
|
view.backgroundColor = .secondarySystemBackground
|
||||||
|
view.subviews.compactMap { $0 as? UIScrollView }.first?.bounces = imageViewControllers.count > 1
|
||||||
|
|
||||||
|
navigationItem.leftBarButtonItem = .init(
|
||||||
|
systemItem: .close,
|
||||||
|
primaryAction: UIAction { [weak self] _ in self?.presentingViewController?.dismiss(animated: true) })
|
||||||
|
|
||||||
|
navigationController?.barHideOnTapGestureRecognizer.addTarget(
|
||||||
|
self,
|
||||||
|
action: #selector(toggleDescriptionVisibility))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension ImagePageViewController {
|
||||||
|
@objc func toggleDescriptionVisibility() {
|
||||||
|
for controller in imageViewControllers {
|
||||||
|
controller.toggleDescriptionVisibility()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension ImagePageViewController: UIPageViewControllerDataSource {
|
||||||
|
func pageViewController(_ pageViewController: UIPageViewController,
|
||||||
|
viewControllerAfter viewController: UIViewController) -> UIViewController? {
|
||||||
|
guard
|
||||||
|
let imageViewController = viewController as? ImageViewController,
|
||||||
|
let index = imageViewControllers.firstIndex(of: imageViewController),
|
||||||
|
index + 1 < imageViewControllers.count
|
||||||
|
else { return nil }
|
||||||
|
|
||||||
|
return imageViewControllers[index + 1]
|
||||||
|
}
|
||||||
|
|
||||||
|
func pageViewController(_ pageViewController: UIPageViewController,
|
||||||
|
viewControllerBefore viewController: UIViewController) -> UIViewController? {
|
||||||
|
guard
|
||||||
|
let imageViewController = viewController as? ImageViewController,
|
||||||
|
let index = imageViewControllers.firstIndex(of: imageViewController),
|
||||||
|
index > 0
|
||||||
|
else { return nil }
|
||||||
|
|
||||||
|
return imageViewControllers[index - 1]
|
||||||
|
}
|
||||||
|
}
|
169
View Controllers/ImageViewController.swift
Normal file
169
View Controllers/ImageViewController.swift
Normal file
|
@ -0,0 +1,169 @@
|
||||||
|
// Copyright © 2020 Metabolist. All rights reserved.
|
||||||
|
|
||||||
|
import Kingfisher
|
||||||
|
import UIKit
|
||||||
|
import ViewModels
|
||||||
|
|
||||||
|
class ImageViewController: UIViewController {
|
||||||
|
private let viewModel: AttachmentViewModel
|
||||||
|
private let scrollView = UIScrollView()
|
||||||
|
private let contentView = UIView()
|
||||||
|
private let imageView = AnimatedImageView()
|
||||||
|
private let playerView = PlayerView()
|
||||||
|
private let descriptionBackgroundView = UIVisualEffectView(effect: UIBlurEffect(style: .systemChromeMaterial))
|
||||||
|
private let descriptionTextView = UITextView()
|
||||||
|
|
||||||
|
init(viewModel: AttachmentViewModel) {
|
||||||
|
self.viewModel = viewModel
|
||||||
|
|
||||||
|
super.init(nibName: nil, bundle: nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(*, unavailable)
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
// swiftlint:disable:next function_body_length
|
||||||
|
override func viewDidLoad() {
|
||||||
|
super.viewDidLoad()
|
||||||
|
|
||||||
|
view.backgroundColor = .secondarySystemBackground
|
||||||
|
|
||||||
|
view.addSubview(scrollView)
|
||||||
|
scrollView.delegate = self
|
||||||
|
scrollView.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
scrollView.showsHorizontalScrollIndicator = false
|
||||||
|
scrollView.showsVerticalScrollIndicator = false
|
||||||
|
scrollView.maximumZoomScale = Self.maximumZoomScale
|
||||||
|
|
||||||
|
contentView.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
scrollView.addSubview(contentView)
|
||||||
|
|
||||||
|
contentView.addSubview(imageView)
|
||||||
|
imageView.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
imageView.contentMode = .scaleAspectFit
|
||||||
|
|
||||||
|
contentView.addSubview(playerView)
|
||||||
|
playerView.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
|
||||||
|
view.addSubview(descriptionBackgroundView)
|
||||||
|
descriptionBackgroundView.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
descriptionBackgroundView.isHidden = viewModel.attachment.description == nil
|
||||||
|
|| viewModel.attachment.description == ""
|
||||||
|
|
||||||
|
descriptionBackgroundView.contentView.addSubview(descriptionTextView)
|
||||||
|
descriptionTextView.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
descriptionTextView.backgroundColor = .clear
|
||||||
|
descriptionTextView.font = .preferredFont(forTextStyle: .caption1)
|
||||||
|
descriptionTextView.adjustsFontForContentSizeCategory = true
|
||||||
|
descriptionTextView.text = viewModel.attachment.description
|
||||||
|
descriptionTextView.isScrollEnabled = false
|
||||||
|
descriptionTextView.isEditable = false
|
||||||
|
|
||||||
|
NSLayoutConstraint.activate([
|
||||||
|
scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
|
||||||
|
scrollView.topAnchor.constraint(equalTo: view.topAnchor),
|
||||||
|
scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
|
||||||
|
scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
|
||||||
|
contentView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor),
|
||||||
|
contentView.topAnchor.constraint(equalTo: scrollView.topAnchor),
|
||||||
|
contentView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor),
|
||||||
|
contentView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor),
|
||||||
|
contentView.centerXAnchor.constraint(equalTo: scrollView.centerXAnchor),
|
||||||
|
contentView.centerYAnchor.constraint(equalTo: scrollView.centerYAnchor),
|
||||||
|
imageView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
|
||||||
|
imageView.topAnchor.constraint(equalTo: contentView.topAnchor),
|
||||||
|
imageView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
|
||||||
|
imageView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
|
||||||
|
playerView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
|
||||||
|
playerView.topAnchor.constraint(equalTo: contentView.topAnchor),
|
||||||
|
playerView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
|
||||||
|
playerView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
|
||||||
|
descriptionBackgroundView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
|
||||||
|
descriptionBackgroundView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
|
||||||
|
descriptionBackgroundView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
|
||||||
|
descriptionTextView.leadingAnchor.constraint(
|
||||||
|
equalTo: descriptionBackgroundView.layoutMarginsGuide.leadingAnchor),
|
||||||
|
descriptionTextView.topAnchor.constraint(equalTo: descriptionBackgroundView.topAnchor),
|
||||||
|
descriptionTextView.trailingAnchor.constraint(
|
||||||
|
equalTo: descriptionBackgroundView.layoutMarginsGuide.trailingAnchor),
|
||||||
|
descriptionTextView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor)
|
||||||
|
])
|
||||||
|
|
||||||
|
switch viewModel.attachment.type {
|
||||||
|
case .image:
|
||||||
|
playerView.isHidden = true
|
||||||
|
imageView.isHidden = false
|
||||||
|
imageView.kf.indicatorType = .activity
|
||||||
|
imageView.kf.setImage(
|
||||||
|
with: viewModel.attachment.previewUrl,
|
||||||
|
options: [.onlyFromCache],
|
||||||
|
completionHandler: { [weak self] in
|
||||||
|
guard let self = self else { return }
|
||||||
|
|
||||||
|
if case .success = $0 {
|
||||||
|
self.imageView.kf.indicatorType = .none
|
||||||
|
}
|
||||||
|
|
||||||
|
self.imageView.kf.setImage(
|
||||||
|
with: self.viewModel.attachment.url,
|
||||||
|
options: [.keepCurrentImageWhileLoading])
|
||||||
|
})
|
||||||
|
case .gifv:
|
||||||
|
playerView.isHidden = false
|
||||||
|
imageView.isHidden = true
|
||||||
|
let player = PlayerCache.shared.player(url: viewModel.attachment.url)
|
||||||
|
|
||||||
|
player.isMuted = true
|
||||||
|
|
||||||
|
playerView.player = player
|
||||||
|
player.play()
|
||||||
|
default: break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension ImageViewController {
|
||||||
|
func toggleDescriptionVisibility() {
|
||||||
|
UIView.animate(withDuration: .shortAnimationDuration) {
|
||||||
|
self.descriptionBackgroundView.alpha = self.descriptionBackgroundView.alpha > 0 ? 0 : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension ImageViewController: UIScrollViewDelegate {
|
||||||
|
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
|
||||||
|
contentView
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://stackoverflow.com/a/40480610/2484482
|
||||||
|
func scrollViewDidZoom(_ scrollView: UIScrollView) {
|
||||||
|
if scrollView.zoomScale > 1,
|
||||||
|
let contentSize = imageView.image?.size ?? playerView.player?.currentItem?.presentationSize {
|
||||||
|
let ratio = min(contentView.frame.width / contentSize.width, contentView.frame.height / contentSize.height)
|
||||||
|
|
||||||
|
let newWidth = contentSize.width * ratio
|
||||||
|
let newHeight = contentSize.height * ratio
|
||||||
|
|
||||||
|
let horizontalInset = 0.5 * (newWidth * scrollView.zoomScale > contentView.frame.width
|
||||||
|
? (newWidth - contentView.frame.width)
|
||||||
|
: (scrollView.frame.width - scrollView.contentSize.width))
|
||||||
|
let verticalInset = 0.5 * (newHeight * scrollView.zoomScale > contentView.frame.height
|
||||||
|
? (newHeight - contentView.frame.height)
|
||||||
|
: (scrollView.frame.height - scrollView.contentSize.height))
|
||||||
|
|
||||||
|
scrollView.contentInset = .init(
|
||||||
|
top: verticalInset,
|
||||||
|
left: horizontalInset,
|
||||||
|
bottom: verticalInset,
|
||||||
|
right: horizontalInset)
|
||||||
|
} else {
|
||||||
|
scrollView.contentInset = .zero
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private extension ImageViewController {
|
||||||
|
static let maximumZoomScale: CGFloat = 5
|
||||||
|
}
|
|
@ -310,7 +310,12 @@ private extension TableViewController {
|
||||||
player.play()
|
player.play()
|
||||||
}
|
}
|
||||||
case .image, .gifv:
|
case .image, .gifv:
|
||||||
break
|
let imagePageViewController = ImagePageViewController(
|
||||||
|
initiallyVisible: attachmentViewModel,
|
||||||
|
statusViewModel: statusViewModel)
|
||||||
|
let imageNavigationController = ImageNavigationController(imagePageViewController: imagePageViewController)
|
||||||
|
|
||||||
|
present(imageNavigationController, animated: true)
|
||||||
case .unknown:
|
case .unknown:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,8 +15,11 @@ class PlayerView: UIView {
|
||||||
|
|
||||||
override init(frame: CGRect) {
|
override init(frame: CGRect) {
|
||||||
super.init(frame: frame)
|
super.init(frame: frame)
|
||||||
|
}
|
||||||
|
|
||||||
(layer as? AVPlayerLayer)?.videoGravity = .resizeAspectFill
|
var videoGravity: AVLayerVideoGravity {
|
||||||
|
get { playerLayer.videoGravity }
|
||||||
|
set { playerLayer.videoGravity = newValue }
|
||||||
}
|
}
|
||||||
|
|
||||||
@available(*, unavailable)
|
@available(*, unavailable)
|
||||||
|
@ -24,3 +27,10 @@ class PlayerView: UIView {
|
||||||
fatalError("init(coder:) has not been implemented")
|
fatalError("init(coder:) has not been implemented")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private extension PlayerView {
|
||||||
|
var playerLayer: AVPlayerLayer {
|
||||||
|
// swiftlint:disable:next force_cast
|
||||||
|
layer as! AVPlayerLayer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -100,6 +100,7 @@ private extension StatusAttachmentView {
|
||||||
|
|
||||||
addSubview(playerView)
|
addSubview(playerView)
|
||||||
playerView.translatesAutoresizingMaskIntoConstraints = false
|
playerView.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
playerView.videoGravity = .resizeAspectFill
|
||||||
playerView.isHidden = true
|
playerView.isHidden = true
|
||||||
|
|
||||||
addSubview(button)
|
addSubview(button)
|
||||||
|
|
|
@ -13,6 +13,7 @@ extension CGFloat {
|
||||||
|
|
||||||
extension TimeInterval {
|
extension TimeInterval {
|
||||||
static let defaultAnimationDuration: Self = 0.5
|
static let defaultAnimationDuration: Self = 0.5
|
||||||
|
static let shortAnimationDuration = defaultAnimationDuration / 2
|
||||||
}
|
}
|
||||||
|
|
||||||
extension UIImage {
|
extension UIImage {
|
||||||
|
|
Loading…
Reference in a new issue