forked from zelo72/mastodon-ios
feat: handle profile avatar preview
This commit is contained in:
parent
6f0b4354a7
commit
acbbafb18f
|
@ -51,7 +51,10 @@ extension AvatarConfigurableView {
|
||||||
avatarConfigurableView(self, didFinishConfiguration: configuration)
|
avatarConfigurableView(self, didFinishConfiguration: configuration)
|
||||||
}
|
}
|
||||||
|
|
||||||
let filter = ScaledToSizeWithRoundedCornersFilter(size: Self.configurableAvatarImageSize, radius: Self.configurableAvatarImageCornerRadius)
|
let filter = ScaledToSizeWithRoundedCornersFilter(
|
||||||
|
size: Self.configurableAvatarImageSize,
|
||||||
|
radius: configuration.keepImageCorner ? 0 : Self.configurableAvatarImageCornerRadius
|
||||||
|
)
|
||||||
|
|
||||||
// set placeholder if no asset
|
// set placeholder if no asset
|
||||||
guard let avatarImageURL = configuration.avatarImageURL else {
|
guard let avatarImageURL = configuration.avatarImageURL else {
|
||||||
|
@ -91,6 +94,12 @@ extension AvatarConfigurableView {
|
||||||
runImageTransitionIfCached: false,
|
runImageTransitionIfCached: false,
|
||||||
completion: nil
|
completion: nil
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if Self.configurableAvatarImageCornerRadius > 0, configuration.keepImageCorner {
|
||||||
|
configurableAvatarImageView?.layer.masksToBounds = true
|
||||||
|
configurableAvatarImageView?.layer.cornerRadius = Self.configurableAvatarImageCornerRadius
|
||||||
|
configurableAvatarImageView?.layer.cornerCurve = Self.configurableAvatarImageCornerRadius < Self.configurableAvatarImageSize.width * 0.5 ? .continuous :.circular
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
configureLayerBorder(view: avatarImageView, configuration: configuration)
|
configureLayerBorder(view: avatarImageView, configuration: configuration)
|
||||||
|
@ -148,16 +157,20 @@ struct AvatarConfigurableViewConfiguration {
|
||||||
let borderColor: UIColor?
|
let borderColor: UIColor?
|
||||||
let borderWidth: CGFloat?
|
let borderWidth: CGFloat?
|
||||||
|
|
||||||
|
let keepImageCorner: Bool
|
||||||
|
|
||||||
init(
|
init(
|
||||||
avatarImageURL: URL?,
|
avatarImageURL: URL?,
|
||||||
placeholderImage: UIImage? = nil,
|
placeholderImage: UIImage? = nil,
|
||||||
borderColor: UIColor? = nil,
|
borderColor: UIColor? = nil,
|
||||||
borderWidth: CGFloat? = nil
|
borderWidth: CGFloat? = nil,
|
||||||
|
keepImageCorner: Bool = true
|
||||||
) {
|
) {
|
||||||
self.avatarImageURL = avatarImageURL
|
self.avatarImageURL = avatarImageURL
|
||||||
self.placeholderImage = placeholderImage
|
self.placeholderImage = placeholderImage
|
||||||
self.borderColor = borderColor
|
self.borderColor = borderColor
|
||||||
self.borderWidth = borderWidth
|
self.borderWidth = borderWidth
|
||||||
|
self.keepImageCorner = keepImageCorner
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -106,6 +106,8 @@ extension MediaPreviewViewController {
|
||||||
mosaicImageViewContainer.setImageViews(alpha: 1)
|
mosaicImageViewContainer.setImageViews(alpha: 1)
|
||||||
mosaicImageViewContainer.setImageView(alpha: 0, index: index)
|
mosaicImageViewContainer.setImageView(alpha: 0, index: index)
|
||||||
}
|
}
|
||||||
|
case .profileAvatar:
|
||||||
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.store(in: &disposeBag)
|
.store(in: &disposeBag)
|
||||||
|
|
|
@ -36,7 +36,7 @@ final class MediaPreviewViewModel: NSObject {
|
||||||
switch entity.type {
|
switch entity.type {
|
||||||
case .image:
|
case .image:
|
||||||
guard let url = URL(string: entity.url) else { continue }
|
guard let url = URL(string: entity.url) else { continue }
|
||||||
let meta = MediaPreviewImageViewModel.StatusImagePreviewMeta(url: url, thumbnail: thumbnail)
|
let meta = MediaPreviewImageViewModel.RemoteImagePreviewMeta(url: url, thumbnail: thumbnail)
|
||||||
let mediaPreviewImageModel = MediaPreviewImageViewModel(meta: meta)
|
let mediaPreviewImageModel = MediaPreviewImageViewModel(meta: meta)
|
||||||
let mediaPreviewImageViewController = MediaPreviewImageViewController()
|
let mediaPreviewImageViewController = MediaPreviewImageViewController()
|
||||||
mediaPreviewImageViewController.viewModel = mediaPreviewImageModel
|
mediaPreviewImageViewController.viewModel = mediaPreviewImageModel
|
||||||
|
@ -52,12 +52,33 @@ final class MediaPreviewViewModel: NSObject {
|
||||||
super.init()
|
super.init()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
init(context: AppContext, meta: ProfileAvatarImagePreviewMeta, pushTransitionItem: MediaPreviewTransitionItem) {
|
||||||
|
self.context = context
|
||||||
|
self.initialItem = .profileAvatar(meta)
|
||||||
|
var viewControllers: [UIViewController] = []
|
||||||
|
let managedObjectContext = self.context.managedObjectContext
|
||||||
|
managedObjectContext.performAndWait {
|
||||||
|
let account = managedObjectContext.object(with: meta.accountObjectID) as! MastodonUser
|
||||||
|
let avatarURL = account.avatarImageURL() ?? URL(string: "https://example.com")! // assert URL exist
|
||||||
|
let meta = MediaPreviewImageViewModel.RemoteImagePreviewMeta(url: avatarURL, thumbnail: meta.preloadThumbnailImage)
|
||||||
|
let mediaPreviewImageModel = MediaPreviewImageViewModel(meta: meta)
|
||||||
|
let mediaPreviewImageViewController = MediaPreviewImageViewController()
|
||||||
|
mediaPreviewImageViewController.viewModel = mediaPreviewImageModel
|
||||||
|
viewControllers.append(mediaPreviewImageViewController)
|
||||||
|
}
|
||||||
|
self.viewControllers = viewControllers
|
||||||
|
self.currentPage = CurrentValueSubject(0)
|
||||||
|
self.pushTransitionItem = pushTransitionItem
|
||||||
|
super.init()
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension MediaPreviewViewModel {
|
extension MediaPreviewViewModel {
|
||||||
|
|
||||||
enum PreviewItem {
|
enum PreviewItem {
|
||||||
case status(StatusImagePreviewMeta)
|
case status(StatusImagePreviewMeta)
|
||||||
|
case profileAvatar(ProfileAvatarImagePreviewMeta)
|
||||||
case local(LocalImagePreviewMeta)
|
case local(LocalImagePreviewMeta)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -67,6 +88,11 @@ extension MediaPreviewViewModel {
|
||||||
let preloadThumbnailImages: [UIImage?]
|
let preloadThumbnailImages: [UIImage?]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct ProfileAvatarImagePreviewMeta {
|
||||||
|
let accountObjectID: NSManagedObjectID
|
||||||
|
let preloadThumbnailImage: UIImage?
|
||||||
|
}
|
||||||
|
|
||||||
struct LocalImagePreviewMeta {
|
struct LocalImagePreviewMeta {
|
||||||
let image: UIImage
|
let image: UIImage
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@ class MediaPreviewImageViewModel {
|
||||||
// input
|
// input
|
||||||
let item: ImagePreviewItem
|
let item: ImagePreviewItem
|
||||||
|
|
||||||
init(meta: StatusImagePreviewMeta) {
|
init(meta: RemoteImagePreviewMeta) {
|
||||||
self.item = .status(meta)
|
self.item = .status(meta)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,11 +25,11 @@ class MediaPreviewImageViewModel {
|
||||||
|
|
||||||
extension MediaPreviewImageViewModel {
|
extension MediaPreviewImageViewModel {
|
||||||
enum ImagePreviewItem {
|
enum ImagePreviewItem {
|
||||||
case status(StatusImagePreviewMeta)
|
case status(RemoteImagePreviewMeta)
|
||||||
case local(LocalImagePreviewMeta)
|
case local(LocalImagePreviewMeta)
|
||||||
}
|
}
|
||||||
|
|
||||||
struct StatusImagePreviewMeta {
|
struct RemoteImagePreviewMeta {
|
||||||
let url: URL
|
let url: URL
|
||||||
let thumbnail: UIImage?
|
let thumbnail: UIImage?
|
||||||
}
|
}
|
||||||
|
|
|
@ -150,8 +150,7 @@ extension ProfileHeaderViewController {
|
||||||
with: AvatarConfigurableViewConfiguration(
|
with: AvatarConfigurableViewConfiguration(
|
||||||
avatarImageURL: image == nil ? url : nil, // set only when image empty
|
avatarImageURL: image == nil ? url : nil, // set only when image empty
|
||||||
placeholderImage: image,
|
placeholderImage: image,
|
||||||
borderColor: .white,
|
keepImageCorner: true // fit preview transitioning
|
||||||
borderWidth: 2
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,8 @@ import UIKit
|
||||||
import ActiveLabel
|
import ActiveLabel
|
||||||
import TwitterTextEditor
|
import TwitterTextEditor
|
||||||
|
|
||||||
protocol ProfileHeaderViewDelegate: class {
|
protocol ProfileHeaderViewDelegate: AnyObject {
|
||||||
|
func profileHeaderView(_ profileHeaderView: ProfileHeaderView, avatarImageViewDidPressed imageView: UIImageView)
|
||||||
func profileHeaderView(_ profileHeaderView: ProfileHeaderView, relationshipButtonDidPressed button: ProfileRelationshipActionButton)
|
func profileHeaderView(_ profileHeaderView: ProfileHeaderView, relationshipButtonDidPressed button: ProfileRelationshipActionButton)
|
||||||
func profileHeaderView(_ profileHeaderView: ProfileHeaderView, activeLabel: ActiveLabel, entityDidPressed entity: ActiveEntity)
|
func profileHeaderView(_ profileHeaderView: ProfileHeaderView, activeLabel: ActiveLabel, entityDidPressed entity: ActiveEntity)
|
||||||
|
|
||||||
|
@ -23,6 +24,8 @@ final class ProfileHeaderView: UIView {
|
||||||
|
|
||||||
static let avatarImageViewSize = CGSize(width: 56, height: 56)
|
static let avatarImageViewSize = CGSize(width: 56, height: 56)
|
||||||
static let avatarImageViewCornerRadius: CGFloat = 6
|
static let avatarImageViewCornerRadius: CGFloat = 6
|
||||||
|
static let avatarImageViewBorderColor = UIColor.white
|
||||||
|
static let avatarImageViewBorderWidth: CGFloat = 2
|
||||||
static let friendshipActionButtonSize = CGSize(width: 108, height: 34)
|
static let friendshipActionButtonSize = CGSize(width: 108, height: 34)
|
||||||
static let bannerImageViewPlaceholderColor = UIColor.systemGray
|
static let bannerImageViewPlaceholderColor = UIColor.systemGray
|
||||||
|
|
||||||
|
@ -51,6 +54,16 @@ final class ProfileHeaderView: UIView {
|
||||||
return overlayView
|
return overlayView
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
let avatarImageViewBackgroundView: UIView = {
|
||||||
|
let view = UIView()
|
||||||
|
view.layer.masksToBounds = true
|
||||||
|
view.layer.cornerRadius = ProfileHeaderView.avatarImageViewCornerRadius
|
||||||
|
view.layer.cornerCurve = .continuous
|
||||||
|
view.layer.borderColor = ProfileHeaderView.avatarImageViewBorderColor.cgColor
|
||||||
|
view.layer.borderWidth = ProfileHeaderView.avatarImageViewBorderWidth
|
||||||
|
return view
|
||||||
|
}()
|
||||||
|
|
||||||
let avatarImageView: UIImageView = {
|
let avatarImageView: UIImageView = {
|
||||||
let imageView = UIImageView()
|
let imageView = UIImageView()
|
||||||
let placeholderImage = UIImage
|
let placeholderImage = UIImage
|
||||||
|
@ -188,6 +201,15 @@ extension ProfileHeaderView {
|
||||||
avatarImageView.heightAnchor.constraint(equalToConstant: ProfileHeaderView.avatarImageViewSize.height).priority(.required - 1),
|
avatarImageView.heightAnchor.constraint(equalToConstant: ProfileHeaderView.avatarImageViewSize.height).priority(.required - 1),
|
||||||
])
|
])
|
||||||
|
|
||||||
|
avatarImageViewBackgroundView.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
bannerContainerView.insertSubview(avatarImageViewBackgroundView, belowSubview: avatarImageView)
|
||||||
|
NSLayoutConstraint.activate([
|
||||||
|
avatarImageView.topAnchor.constraint(equalTo: avatarImageViewBackgroundView.topAnchor, constant: ProfileHeaderView.avatarImageViewBorderWidth),
|
||||||
|
avatarImageView.leadingAnchor.constraint(equalTo: avatarImageViewBackgroundView.leadingAnchor, constant: ProfileHeaderView.avatarImageViewBorderWidth),
|
||||||
|
avatarImageViewBackgroundView.trailingAnchor.constraint(equalTo: avatarImageView.trailingAnchor, constant: ProfileHeaderView.avatarImageViewBorderWidth),
|
||||||
|
avatarImageViewBackgroundView.bottomAnchor.constraint(equalTo: avatarImageView.bottomAnchor, constant: ProfileHeaderView.avatarImageViewBorderWidth),
|
||||||
|
])
|
||||||
|
|
||||||
editAvatarBackgroundView.translatesAutoresizingMaskIntoConstraints = false
|
editAvatarBackgroundView.translatesAutoresizingMaskIntoConstraints = false
|
||||||
avatarImageView.addSubview(editAvatarBackgroundView)
|
avatarImageView.addSubview(editAvatarBackgroundView)
|
||||||
NSLayoutConstraint.activate([
|
NSLayoutConstraint.activate([
|
||||||
|
@ -313,6 +335,9 @@ extension ProfileHeaderView {
|
||||||
|
|
||||||
bioActiveLabel.delegate = self
|
bioActiveLabel.delegate = self
|
||||||
|
|
||||||
|
let avatarImageViewSingleTapGestureRecognizer = UITapGestureRecognizer.singleTapGestureRecognizer
|
||||||
|
avatarImageView.addGestureRecognizer(avatarImageViewSingleTapGestureRecognizer)
|
||||||
|
avatarImageViewSingleTapGestureRecognizer.addTarget(self, action: #selector(ProfileHeaderView.avatarImageViewDidPressed(_:)))
|
||||||
relationshipActionButton.addTarget(self, action: #selector(ProfileHeaderView.relationshipActionButtonDidPressed(_:)), for: .touchUpInside)
|
relationshipActionButton.addTarget(self, action: #selector(ProfileHeaderView.relationshipActionButtonDidPressed(_:)), for: .touchUpInside)
|
||||||
|
|
||||||
configure(state: .normal)
|
configure(state: .normal)
|
||||||
|
@ -372,6 +397,11 @@ extension ProfileHeaderView {
|
||||||
assert(sender === relationshipActionButton)
|
assert(sender === relationshipActionButton)
|
||||||
delegate?.profileHeaderView(self, relationshipButtonDidPressed: relationshipActionButton)
|
delegate?.profileHeaderView(self, relationshipButtonDidPressed: relationshipActionButton)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@objc private func avatarImageViewDidPressed(_ sender: UITapGestureRecognizer) {
|
||||||
|
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
|
||||||
|
delegate?.profileHeaderView(self, avatarImageViewDidPressed: avatarImageView)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - ActiveLabelDelegate
|
// MARK: - ActiveLabelDelegate
|
||||||
|
|
|
@ -10,7 +10,7 @@ import UIKit
|
||||||
import Combine
|
import Combine
|
||||||
import ActiveLabel
|
import ActiveLabel
|
||||||
|
|
||||||
final class ProfileViewController: UIViewController, NeedsDependency {
|
final class ProfileViewController: UIViewController, NeedsDependency, MediaPreviewableViewController {
|
||||||
|
|
||||||
weak var context: AppContext! { willSet { precondition(!isViewLoaded) } }
|
weak var context: AppContext! { willSet { precondition(!isViewLoaded) } }
|
||||||
weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } }
|
weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } }
|
||||||
|
@ -18,6 +18,8 @@ final class ProfileViewController: UIViewController, NeedsDependency {
|
||||||
var disposeBag = Set<AnyCancellable>()
|
var disposeBag = Set<AnyCancellable>()
|
||||||
var viewModel: ProfileViewModel!
|
var viewModel: ProfileViewModel!
|
||||||
|
|
||||||
|
let mediaPreviewTransitionController = MediaPreviewTransitionController()
|
||||||
|
|
||||||
private(set) lazy var cancelEditingBarButtonItem: UIBarButtonItem = {
|
private(set) lazy var cancelEditingBarButtonItem: UIBarButtonItem = {
|
||||||
let barButtonItem = UIBarButtonItem(title: L10n.Common.Controls.Actions.cancel, style: .plain, target: self, action: #selector(ProfileViewController.cancelEditingBarButtonItemPressed(_:)))
|
let barButtonItem = UIBarButtonItem(title: L10n.Common.Controls.Actions.cancel, style: .plain, target: self, action: #selector(ProfileViewController.cancelEditingBarButtonItemPressed(_:)))
|
||||||
barButtonItem.tintColor = .white
|
barButtonItem.tintColor = .white
|
||||||
|
@ -645,6 +647,38 @@ extension ProfileViewController: ProfilePagingViewControllerDelegate {
|
||||||
// MARK: - ProfileHeaderViewDelegate
|
// MARK: - ProfileHeaderViewDelegate
|
||||||
extension ProfileViewController: ProfileHeaderViewDelegate {
|
extension ProfileViewController: ProfileHeaderViewDelegate {
|
||||||
|
|
||||||
|
func profileHeaderView(_ profileHeaderView: ProfileHeaderView, avatarImageViewDidPressed imageView: UIImageView) {
|
||||||
|
guard let mastodonUser = viewModel.mastodonUser.value else { return }
|
||||||
|
guard let avatar = imageView.image else { return }
|
||||||
|
|
||||||
|
let meta = MediaPreviewViewModel.ProfileAvatarImagePreviewMeta(
|
||||||
|
accountObjectID: mastodonUser.objectID,
|
||||||
|
preloadThumbnailImage: avatar
|
||||||
|
)
|
||||||
|
let pushTransitionItem = MediaPreviewTransitionItem(
|
||||||
|
source: .profileAvatar(profileHeaderView),
|
||||||
|
previewableViewController: self
|
||||||
|
)
|
||||||
|
pushTransitionItem.aspectRatio = CGSize(width: 100, height: 100)
|
||||||
|
pushTransitionItem.sourceImageView = imageView
|
||||||
|
pushTransitionItem.sourceImageViewCornerRadius = ProfileHeaderView.avatarImageViewCornerRadius
|
||||||
|
pushTransitionItem.initialFrame = {
|
||||||
|
let initialFrame = imageView.superview!.convert(imageView.frame, to: nil)
|
||||||
|
assert(initialFrame != .zero)
|
||||||
|
return initialFrame
|
||||||
|
}()
|
||||||
|
pushTransitionItem.image = avatar
|
||||||
|
|
||||||
|
let mediaPreviewViewModel = MediaPreviewViewModel(
|
||||||
|
context: context,
|
||||||
|
meta: meta,
|
||||||
|
pushTransitionItem: pushTransitionItem
|
||||||
|
)
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self.coordinator.present(scene: .mediaPreview(viewModel: mediaPreviewViewModel), from: self, transition: .custom(transitioningDelegate: self.mediaPreviewTransitionController))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func profileHeaderView(_ profileHeaderView: ProfileHeaderView, relationshipButtonDidPressed button: ProfileRelationshipActionButton) {
|
func profileHeaderView(_ profileHeaderView: ProfileHeaderView, relationshipButtonDidPressed button: ProfileRelationshipActionButton) {
|
||||||
let relationshipActionSet = viewModel.relationshipActionOptionSet.value
|
let relationshipActionSet = viewModel.relationshipActionOptionSet.value
|
||||||
if relationshipActionSet.contains(.edit) {
|
if relationshipActionSet.contains(.edit) {
|
||||||
|
|
|
@ -58,10 +58,7 @@ extension MediaHostToMediaPreviewViewControllerAnimatedTransitioning {
|
||||||
// set to image hidden
|
// set to image hidden
|
||||||
toVC.pagingViewConttroller.view.alpha = 0
|
toVC.pagingViewConttroller.view.alpha = 0
|
||||||
// set from image hidden. update hidden when paging. seealso: `MediaPreviewViewController`
|
// set from image hidden. update hidden when paging. seealso: `MediaPreviewViewController`
|
||||||
switch transitionItem.source {
|
transitionItem.source.updateAppearance(position: .start, index: toVC.viewModel.currentPage.value)
|
||||||
case .mosaic(let mosaicImageViewContainer):
|
|
||||||
mosaicImageViewContainer.setImageView(alpha: 0, index: toVC.viewModel.currentPage.value)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set transition image view
|
// Set transition image view
|
||||||
assert(transitionItem.initialFrame != nil)
|
assert(transitionItem.initialFrame != nil)
|
||||||
|
@ -143,16 +140,14 @@ extension MediaHostToMediaPreviewViewControllerAnimatedTransitioning {
|
||||||
} else {
|
} else {
|
||||||
fromView.alpha = 0
|
fromView.alpha = 0
|
||||||
}
|
}
|
||||||
|
self.transitionItem.sourceImageViewCornerRadius.flatMap { self.transitionItem.snapshotTransitioning?.layer.cornerRadius = $0 }
|
||||||
fromVC.closeButtonBackground.alpha = 0
|
fromVC.closeButtonBackground.alpha = 0
|
||||||
fromVC.visualEffectView.effect = nil
|
fromVC.visualEffectView.effect = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
animator.addCompletion { position in
|
animator.addCompletion { position in
|
||||||
self.transitionItem.snapshotTransitioning?.removeFromSuperview()
|
self.transitionItem.snapshotTransitioning?.removeFromSuperview()
|
||||||
switch self.transitionItem.source {
|
self.transitionItem.source.updateAppearance(position: position, index: nil)
|
||||||
case .mosaic(let mosaicImageViewContainer):
|
|
||||||
mosaicImageViewContainer.setImageViews(alpha: 1)
|
|
||||||
}
|
|
||||||
transitionContext.completeTransition(position == .end)
|
transitionContext.completeTransition(position == .end)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -222,6 +217,7 @@ extension MediaHostToMediaPreviewViewControllerAnimatedTransitioning {
|
||||||
animator.addAnimations {
|
animator.addAnimations {
|
||||||
fromVC.closeButtonBackground.alpha = 0
|
fromVC.closeButtonBackground.alpha = 0
|
||||||
fromVC.visualEffectView.effect = nil
|
fromVC.visualEffectView.effect = nil
|
||||||
|
self.transitionItem.sourceImageViewCornerRadius.flatMap { self.transitionItem.snapshotTransitioning?.layer.cornerRadius = $0 }
|
||||||
}
|
}
|
||||||
|
|
||||||
animator.addCompletion { position in
|
animator.addCompletion { position in
|
||||||
|
@ -231,10 +227,8 @@ extension MediaHostToMediaPreviewViewControllerAnimatedTransitioning {
|
||||||
self.transitionItem.snapshotRaw?.alpha = position == .start ? 1.0 : 0.0
|
self.transitionItem.snapshotRaw?.alpha = position == .start ? 1.0 : 0.0
|
||||||
self.transitionItem.snapshotTransitioning?.removeFromSuperview()
|
self.transitionItem.snapshotTransitioning?.removeFromSuperview()
|
||||||
if position == .end {
|
if position == .end {
|
||||||
switch self.transitionItem.source {
|
// reset appearance
|
||||||
case .mosaic(let mosaicImageViewContainer):
|
self.transitionItem.source.updateAppearance(position: position, index: nil)
|
||||||
mosaicImageViewContainer.setImageViews(alpha: 1)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
fromVC.visualEffectView.effect = position == .end ? nil : blurEffect
|
fromVC.visualEffectView.effect = position == .end ? nil : blurEffect
|
||||||
transitionContext.completeTransition(position == .end)
|
transitionContext.completeTransition(position == .end)
|
||||||
|
|
|
@ -20,6 +20,7 @@ class MediaPreviewTransitionItem: Identifiable {
|
||||||
var aspectRatio: CGSize?
|
var aspectRatio: CGSize?
|
||||||
var initialFrame: CGRect? = nil
|
var initialFrame: CGRect? = nil
|
||||||
var sourceImageView: UIImageView?
|
var sourceImageView: UIImageView?
|
||||||
|
var sourceImageViewCornerRadius: CGFloat?
|
||||||
|
|
||||||
// target
|
// target
|
||||||
var targetFrame: CGRect? = nil
|
var targetFrame: CGRect? = nil
|
||||||
|
@ -41,5 +42,20 @@ class MediaPreviewTransitionItem: Identifiable {
|
||||||
extension MediaPreviewTransitionItem {
|
extension MediaPreviewTransitionItem {
|
||||||
enum Source {
|
enum Source {
|
||||||
case mosaic(MosaicImageViewContainer)
|
case mosaic(MosaicImageViewContainer)
|
||||||
|
case profileAvatar(ProfileHeaderView)
|
||||||
|
|
||||||
|
func updateAppearance(position: UIViewAnimatingPosition, index: Int?) {
|
||||||
|
let alpha: CGFloat = position == .end ? 1 : 0
|
||||||
|
switch self {
|
||||||
|
case .mosaic(let mosaicImageViewContainer):
|
||||||
|
if let index = index {
|
||||||
|
mosaicImageViewContainer.setImageView(alpha: 0, index: index)
|
||||||
|
} else {
|
||||||
|
mosaicImageViewContainer.setImageViews(alpha: alpha)
|
||||||
|
}
|
||||||
|
case .profileAvatar(let profileHeaderView):
|
||||||
|
profileHeaderView.avatarImageView.alpha = alpha
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,8 @@ extension MediaPreviewableViewController {
|
||||||
guard index < mosaicImageViewContainer.imageViews.count else { return nil }
|
guard index < mosaicImageViewContainer.imageViews.count else { return nil }
|
||||||
let imageView = mosaicImageViewContainer.imageViews[index]
|
let imageView = mosaicImageViewContainer.imageViews[index]
|
||||||
return imageView.superview!.convert(imageView.frame, to: nil)
|
return imageView.superview!.convert(imageView.frame, to: nil)
|
||||||
|
case .profileAvatar(let profileHeaderView):
|
||||||
|
return profileHeaderView.avatarImageView.superview!.convert(profileHeaderView.avatarImageView.frame, to: nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue