diff --git a/Example/Resources/1.mp4 b/Example/Resources/1.mp4 new file mode 100644 index 0000000..81d11df Binary files /dev/null and b/Example/Resources/1.mp4 differ diff --git a/Example/Resources/2.mp4 b/Example/Resources/2.mp4 new file mode 100644 index 0000000..6aca024 Binary files /dev/null and b/Example/Resources/2.mp4 differ diff --git a/Example/Resources/3.mp4 b/Example/Resources/3.mp4 new file mode 100644 index 0000000..8c9b201 Binary files /dev/null and b/Example/Resources/3.mp4 differ diff --git a/Example/Resources/4.mp4 b/Example/Resources/4.mp4 new file mode 100644 index 0000000..1fc4788 Binary files /dev/null and b/Example/Resources/4.mp4 differ diff --git a/Example/Resources/5.mp4 b/Example/Resources/5.mp4 new file mode 100644 index 0000000..eeb6cef Binary files /dev/null and b/Example/Resources/5.mp4 differ diff --git a/Example/Resources/6.mp4 b/Example/Resources/6.mp4 new file mode 100644 index 0000000..e8497ae Binary files /dev/null and b/Example/Resources/6.mp4 differ diff --git a/Example/Resources/Assets.xcassets/playOverlay.imageset/Contents.json b/Example/Resources/Assets.xcassets/playOverlay.imageset/Contents.json new file mode 100644 index 0000000..db7ffa0 --- /dev/null +++ b/Example/Resources/Assets.xcassets/playOverlay.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "play.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "play@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "play@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Example/Resources/Assets.xcassets/playOverlay.imageset/play.png b/Example/Resources/Assets.xcassets/playOverlay.imageset/play.png new file mode 100644 index 0000000..0f874d0 Binary files /dev/null and b/Example/Resources/Assets.xcassets/playOverlay.imageset/play.png differ diff --git a/Example/Resources/Assets.xcassets/playOverlay.imageset/play@2x.png b/Example/Resources/Assets.xcassets/playOverlay.imageset/play@2x.png new file mode 100644 index 0000000..b369d4a Binary files /dev/null and b/Example/Resources/Assets.xcassets/playOverlay.imageset/play@2x.png differ diff --git a/Example/Resources/Assets.xcassets/playOverlay.imageset/play@3x.png b/Example/Resources/Assets.xcassets/playOverlay.imageset/play@3x.png new file mode 100644 index 0000000..e827b2e Binary files /dev/null and b/Example/Resources/Assets.xcassets/playOverlay.imageset/play@3x.png differ diff --git a/Example/Resources/Base.lproj/Main.storyboard b/Example/Resources/Base.lproj/Main.storyboard index 7775f5e..10bb668 100644 --- a/Example/Resources/Base.lproj/Main.storyboard +++ b/Example/Resources/Base.lproj/Main.storyboard @@ -1,18 +1,18 @@ - + - + - + @@ -24,7 +24,7 @@ - + @@ -46,8 +46,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + @@ -72,10 +108,14 @@ + + + + diff --git a/Example/VideoCell.swift b/Example/VideoCell.swift new file mode 100644 index 0000000..16f4bee --- /dev/null +++ b/Example/VideoCell.swift @@ -0,0 +1,5 @@ +import UIKit + +class VideoCell: UICollectionViewCell { + @IBOutlet var imageView: UIImageView! +} diff --git a/Example/ViewController.swift b/Example/ViewController.swift index 0e42d51..2bc7c99 100644 --- a/Example/ViewController.swift +++ b/Example/ViewController.swift @@ -1,26 +1,39 @@ import UIKit import SimpleImageViewer +import AVFoundation class ViewController: UICollectionViewController, UICollectionViewDelegateFlowLayout { - fileprivate let contentModes: [UIViewContentMode] = [.scaleToFill, - .scaleAspectFit, - .scaleAspectFill, - .center, - .top, - .bottom, - .left, - .right, - .topLeft, - .topRight, - .bottomLeft, - .bottomRight] + private let contentModes: [UIViewContentMode] = [.scaleAspectFill, + .scaleAspectFit, + .scaleToFill, + .center, + .top, + .bottom, + .left, + .right, + .topLeft, + .topRight, + .bottomLeft, + .bottomRight] - fileprivate let images = [UIImage(named: "1"), - UIImage(named: "2"), - UIImage(named: "3"), - UIImage(named: "4"), - UIImage(named: "5"), - UIImage(named: "6")] + private let images = [UIImage(named: "1"), + UIImage(named: "2"), + UIImage(named: "3"), + UIImage(named: "4"), + UIImage(named: "5"), + UIImage(named: "6")] + + private let videos = [AVAsset(url: URL(fileURLWithPath: Bundle.main.path(forResource: "1", ofType: "mp4")!)), + AVAsset(url: URL(fileURLWithPath: Bundle.main.path(forResource: "2", ofType: "mp4")!)), + AVAsset(url: URL(fileURLWithPath: Bundle.main.path(forResource: "3", ofType: "mp4")!)), + AVAsset(url: URL(fileURLWithPath: Bundle.main.path(forResource: "4", ofType: "mp4")!)), + AVAsset(url: URL(fileURLWithPath: Bundle.main.path(forResource: "5", ofType: "mp4")!)), + AVAsset(url: URL(fileURLWithPath: Bundle.main.path(forResource: "6", ofType: "mp4")!))] + + private lazy var thumbnails: [UIImage?] = { + let thumbnailSize = CGSize(width: 300, height: 300) + return self.videos.map { Utilities.screenShot(fromAsset: $0, atTime: CMTimeMake(0, 1), size: thumbnailSize)} + }() override func numberOfSections(in collectionView: UICollectionView) -> Int { return contentModes.count @@ -28,33 +41,57 @@ class ViewController: UICollectionViewController, UICollectionViewDelegateFlowLa override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { - return images.count + return images.count + videos.count } override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { - let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "ImageCell", for: indexPath) as! ImageCell - cell.imageView.image = images[indexPath.row] - cell.imageView.contentMode = contentModes[indexPath.section] - return cell + if indexPath.row % 2 == 0 { + let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "ImageCell", for: indexPath) as! ImageCell + cell.imageView.image = images[indexPath.row / 2] + cell.imageView.contentMode = contentModes[indexPath.section] + return cell + } else { + let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "VideoCell", for: indexPath) as! VideoCell + cell.imageView.image = thumbnails[(indexPath.row + 1) / 2 - 1] + cell.imageView.contentMode = contentModes[indexPath.section] + return cell + } } override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { - let cell = collectionView.cellForItem(at: indexPath) as! ImageCell + var asset: Asset! + + if let cell = collectionView.cellForItem(at: indexPath) as? ImageCell { + asset = Asset.image(imageView: cell.imageView, image: nil, imageBlock: nil) + } else if let cell = collectionView.cellForItem(at: indexPath) as? VideoCell { + let video = videos[(indexPath.row + 1) / 2 - 1] + asset = Asset.video(imageView: cell.imageView, video: video) + } let configuration = ImageViewerConfiguration { config in - config.imageView = cell.imageView + config.asset = asset } - present(ImageViewerController(configuration: configuration), animated: true) + present(GalleryViewController(configuration: configuration), animated: true) } - override func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView { - let headerView = collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionElementKindSectionHeader, withReuseIdentifier: "HeaderView", for: indexPath) as! HeaderView + override func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, + at indexPath: IndexPath) -> UICollectionReusableView { + let headerView = collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionElementKindSectionHeader, + withReuseIdentifier: "HeaderView", + for: indexPath) as! HeaderView headerView.titleLabel.text = contentModes[indexPath.section].name return headerView } - func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { + override func willTransition(to newCollection: UITraitCollection, + with coordinator: UIViewControllerTransitionCoordinator) { + collectionView?.collectionViewLayout.invalidateLayout() + } + + + func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, + sizeForItemAt indexPath: IndexPath) -> CGSize { let cellWidth = collectionView.frame.width / 3 - 8 return CGSize(width: cellWidth, height: cellWidth) } diff --git a/ImageViewer/AnimatableImageView.swift b/ImageViewer/AnimatableImageView.swift index e8e7bfd..eec8f52 100644 --- a/ImageViewer/AnimatableImageView.swift +++ b/ImageViewer/AnimatableImageView.swift @@ -1,7 +1,7 @@ import UIKit final class AnimatableImageView: UIView { - fileprivate let imageView = UIImageView() + private let imageView = UIImageView() override var contentMode: UIViewContentMode { didSet { update() } diff --git a/ImageViewer/AssetViewController/AssetViewController.swift b/ImageViewer/AssetViewController/AssetViewController.swift new file mode 100644 index 0000000..3b1a666 --- /dev/null +++ b/ImageViewer/AssetViewController/AssetViewController.swift @@ -0,0 +1,103 @@ +import UIKit + +protocol AssetViewPanGestureDelegate: class { + func didStartPanGesture() + func didChangePanGesture(translation: CGPoint) + func didEndPanGesture(translation: CGPoint, velocity: CGPoint) +} + +public class AssetViewController: UIViewController { + @IBOutlet var scrollView: UIScrollView! + @IBOutlet var assetView: UIView! + + weak var assetPanGestureDelegate: AssetViewPanGestureDelegate? + var assetSize: CGSize? { return .zero } + + public override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) { + super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) + } + + required public init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override public func viewDidLoad() { + super.viewDidLoad() + setupScrollView() + setupGestureRecognizers() + } + + @objc func assetViewPanned(_ recognizer: UIPanGestureRecognizer) { + let translation = recognizer.translation(in: assetView) + let velocity = recognizer.velocity(in: assetView) + + switch recognizer.state { + case .began: + assetPanGestureDelegate?.didStartPanGesture() + case .changed: + assetPanGestureDelegate?.didChangePanGesture(translation: translation) + case .ended, .cancelled: + assetPanGestureDelegate?.didEndPanGesture(translation: translation, velocity: velocity) + default: break + } + } +} + +extension AssetViewController: UIScrollViewDelegate { + public func viewForZooming(in scrollView: UIScrollView) -> UIView? { + return assetView + } + + public func scrollViewDidZoom(_ scrollView: UIScrollView) { + guard let assetSize = assetSize else { return } + let assetViewSize = Utilities.aspectFitRect(forSize: assetSize, insideRect: assetView.frame) + let verticalInsets = -(scrollView.contentSize.height - max(assetViewSize.height, scrollView.bounds.height)) / 2 + let horizontalInsets = -(scrollView.contentSize.width - max(assetViewSize.width, scrollView.bounds.width)) / 2 + scrollView.contentInset = UIEdgeInsets(top: verticalInsets, + left: horizontalInsets, + bottom: verticalInsets, + right: horizontalInsets) + } +} + +extension AssetViewController: UIGestureRecognizerDelegate { + public func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { + return scrollView.zoomScale == scrollView.minimumZoomScale + } +} + +private extension AssetViewController { + func setupScrollView() { + scrollView.decelerationRate = UIScrollViewDecelerationRateFast + scrollView.showsVerticalScrollIndicator = false + scrollView.showsHorizontalScrollIndicator = false + scrollView.alwaysBounceVertical = true + scrollView.alwaysBounceHorizontal = true + scrollView.delegate = self + scrollView.minimumZoomScale = 1.0 + scrollView.maximumZoomScale = 6.0 + } + + func setupGestureRecognizers() { + let tapGestureRecognizer = UITapGestureRecognizer() + tapGestureRecognizer.numberOfTapsRequired = 2 + tapGestureRecognizer.addTarget(self, action: #selector(assetViewDoubleTapped(_:))) + assetView.addGestureRecognizer(tapGestureRecognizer) + + let panGestureRecognizer = UIPanGestureRecognizer() + panGestureRecognizer.addTarget(self, action: #selector(assetViewPanned(_:))) + panGestureRecognizer.delegate = self + assetView.addGestureRecognizer(panGestureRecognizer) + } + + @objc func assetViewDoubleTapped(_ recognizer: UITapGestureRecognizer) { + if scrollView.zoomScale > scrollView.minimumZoomScale { + scrollView.setZoomScale(scrollView.minimumZoomScale, animated: true) + } else { + let zoomRect = Utilities.zoomRect(forScale: scrollView.maximumZoomScale, + center: recognizer.location(in: recognizer.view), + inRect: assetView.frame) + scrollView.zoom(to: zoomRect, animated: true) + } + } +} diff --git a/ImageViewer/AssetViewController/ImageViewerController/ImageViewController.swift b/ImageViewer/AssetViewController/ImageViewerController/ImageViewController.swift new file mode 100644 index 0000000..fa5af10 --- /dev/null +++ b/ImageViewer/AssetViewController/ImageViewerController/ImageViewController.swift @@ -0,0 +1,52 @@ +import UIKit +import AVFoundation + +public final class ImageViewController: AssetViewController { + @IBOutlet private var imageView: UIImageView! + @IBOutlet private var activityIndicator: UIActivityIndicatorView! + + private var fromImageView: UIImageView? + private var fromImage: UIImage? + private var fromImageBlock: ImageBlock? + + public init(imageView: UIImageView?, image: UIImage?, imageBlock: ImageBlock?) { + super.init(nibName: String(describing: type(of: self)), bundle: Bundle(for: type(of: self))) + + fromImage = image + fromImageView = imageView + fromImageBlock = imageBlock + } + + required public init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override public func viewDidLoad() { + super.viewDidLoad() + imageView.image = fromImageView?.image ?? fromImage + setupActivityIndicator() + } + + override var assetSize: CGSize? { + return imageView.image?.size + } +} + +extension ImageViewController: TransitionDismissable { + var dismissAssetView: UIView { return assetView } + var dismissImage: UIImage? { return imageView.image } +} + +private extension ImageViewController { + func setupActivityIndicator() { + guard let block = fromImageBlock else { return } + activityIndicator.startAnimating() + block { [weak self] image in + guard let image = image else { return } + DispatchQueue.main.async { + self?.activityIndicator.stopAnimating() + self?.imageView.image = image + } + } + } +} diff --git a/ImageViewer/Resources/ImageViewerController.xib b/ImageViewer/AssetViewController/ImageViewerController/ImageViewController.xib similarity index 73% rename from ImageViewer/Resources/ImageViewerController.xib rename to ImageViewer/AssetViewController/ImageViewerController/ImageViewController.xib index 07595fc..e47e683 100644 --- a/ImageViewer/Resources/ImageViewerController.xib +++ b/ImageViewer/AssetViewController/ImageViewerController/ImageViewController.xib @@ -1,18 +1,18 @@ - + - - + - + + @@ -44,26 +44,11 @@ - - + - - @@ -74,7 +59,4 @@ - - - diff --git a/ImageViewer/AssetViewController/VideoViewerController/VideoViewController.swift b/ImageViewer/AssetViewController/VideoViewerController/VideoViewController.swift new file mode 100644 index 0000000..7664008 --- /dev/null +++ b/ImageViewer/AssetViewController/VideoViewerController/VideoViewController.swift @@ -0,0 +1,69 @@ + +import UIKit +import AVFoundation + +public final class VideoViewController: AssetViewController { + private var playerItem: AVPlayerItem + private var player: AVPlayer? + private var playerOutput: AVPlayerItemVideoOutput! + private var playerLayer: AVPlayerLayer! + + public init(imageView: UIImageView?, video: AVAsset) { + self.playerItem = AVPlayerItem(asset: video) + super.init(nibName: String(describing: type(of: self)), bundle: Bundle(for: type(of: self))) + } + + required public init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override public func viewDidLayoutSubviews() { + super.viewDidLayoutSubviews() + playerLayer?.frame = view.bounds + } + + override public func viewDidLoad() { + super.viewDidLoad() + setupVideoLayer() + } + + public override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + player?.play() + } + + override var assetSize: CGSize? { + return playerItem.asset.tracks(withMediaType: AVMediaType.video).first?.naturalSize + } + + func setupVideoLayer() { + let settings = [String(kCVPixelBufferPixelFormatTypeKey): NSNumber(value:kCVPixelFormatType_32BGRA)] + player = AVPlayer(playerItem: playerItem) + playerOutput = AVPlayerItemVideoOutput(pixelBufferAttributes: settings) + playerItem.add(playerOutput) + playerLayer = AVPlayerLayer(player: player) + playerLayer.videoGravity = .resizeAspect + assetView.layer.addSublayer(playerLayer) + } + + @objc override func assetViewPanned(_ recognizer: UIPanGestureRecognizer) { + super.assetViewPanned(recognizer) + + switch recognizer.state { + case .began: + player?.pause() + case .ended, .cancelled: + player?.play() + default: break + } + } +} + +extension VideoViewController: TransitionDismissable { + var dismissAssetView: UIView { return assetView } + var dismissImage: UIImage? { + guard let pixelBuffer = playerOutput.copyPixelBuffer(forItemTime: playerItem.currentTime(), + itemTimeForDisplay: nil) else { return nil } + return Utilities.image(fromPixelBuffer: pixelBuffer) + } +} diff --git a/ImageViewer/AssetViewController/VideoViewerController/VideoViewController.xib b/ImageViewer/AssetViewController/VideoViewerController/VideoViewController.xib new file mode 100644 index 0000000..aff2440 --- /dev/null +++ b/ImageViewer/AssetViewController/VideoViewerController/VideoViewController.xib @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ImageViewer/GalleryViewController.swift b/ImageViewer/GalleryViewController.swift new file mode 100644 index 0000000..46f47c6 --- /dev/null +++ b/ImageViewer/GalleryViewController.swift @@ -0,0 +1,96 @@ +import UIKit + +protocol TransitionDismissable { + var dismissAssetView: UIView { get } + var dismissImage: UIImage? { get } +} + +public final class GalleryViewController: UIViewController { + private var transitionHandler: ImageViewerTransitioningHandler? + private let configuration: ImageViewerConfiguration + private var childViewController: TransitionDismissable! + + @IBOutlet private var containerView: UIView! + + public override var prefersStatusBarHidden: Bool { + return true + } + + public init(configuration: ImageViewerConfiguration) { + self.configuration = configuration + super.init(nibName: String(describing: type(of: self)), bundle: Bundle(for: type(of: self))) + + modalPresentationStyle = .overFullScreen + modalTransitionStyle = .crossDissolve + modalPresentationCapturesStatusBarAppearance = true + } + + required public init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override public func viewDidLoad() { + super.viewDidLoad() + + setupChildAssetViewController() + setupTransitions() + } + + func setupChildAssetViewController() { + let viewController = configuration.asset.assetViewController + viewController.assetPanGestureDelegate = self + childViewController = viewController + addChildViewController(viewController) + containerView.addSubview(viewController.view) + viewController.view.frame = containerView.bounds + viewController.didMove(toParentViewController: self) + } + + func setupTransitions() { + guard let imageView = configuration.asset.imageView, + let assetView = childViewController?.dismissAssetView else { return } + transitionHandler = ImageViewerTransitioningHandler(fromImageView: imageView, toAssetView: assetView) + transitioningDelegate = transitionHandler + } + + @IBAction func closeButtonPressed() { + transitionHandler?.update(dismissImage: childViewController.dismissImage) + dismiss(animated: true) + } +} + +extension GalleryViewController: AssetViewPanGestureDelegate { + func didStartPanGesture() { + transitionHandler?.update(dismissImage: childViewController.dismissImage) + transitionHandler?.dismissInteractively = true + dismiss(animated: true) + } + + func didChangePanGesture(translation: CGPoint) { + let percentage = abs(translation.y) / view.bounds.height + let transform = CGAffineTransform(translationX: translation.x, y: translation.y) + transitionHandler?.dismissalInteractor.update(percentage: percentage) + transitionHandler?.dismissalInteractor.update(transform: transform) + } + + func didEndPanGesture(translation: CGPoint, velocity: CGPoint) { + transitionHandler?.dismissInteractively = false + let percentage = abs(translation.y + velocity.y) / view.bounds.height + if percentage > 0.25 { + transitionHandler?.dismissalInteractor.finish() + } else { + transitionHandler?.dismissalInteractor.cancel() + } + } +} + +private extension Asset { + var assetViewController: AssetViewController & TransitionDismissable { + switch self { + case let .image(imageView, image, imageBlock): + return ImageViewController(imageView: imageView, image: image, imageBlock: imageBlock) + case let .video(imageView, video): + return VideoViewController(imageView: imageView, video: video) + } + } +} diff --git a/ImageViewer/GalleryViewController.xib b/ImageViewer/GalleryViewController.xib new file mode 100644 index 0000000..9e594e4 --- /dev/null +++ b/ImageViewer/GalleryViewController.xib @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ImageViewer/ImageViewerConfiguration.swift b/ImageViewer/ImageViewerConfiguration.swift index 0084f02..e287c13 100644 --- a/ImageViewer/ImageViewerConfiguration.swift +++ b/ImageViewer/ImageViewerConfiguration.swift @@ -1,13 +1,28 @@ import Foundation import UIKit +import AVFoundation + +public enum Asset { + case image(imageView: UIImageView?, image: UIImage?, imageBlock: ImageBlock?) + case video(imageView: UIImageView?, video: AVAsset) +} + +extension Asset { + var imageView: UIImageView? { + switch self { + case let .image(imageView, _, _): + return imageView + case let .video(imageView, _): + return imageView + } + } +} public typealias ImageCompletion = (UIImage?) -> Void public typealias ImageBlock = (@escaping ImageCompletion) -> Void public final class ImageViewerConfiguration { - public var image: UIImage? - public var imageView: UIImageView? - public var imageBlock: ImageBlock? + public var asset: Asset! public typealias ConfigurationClosure = (ImageViewerConfiguration) -> () diff --git a/ImageViewer/ImageViewerController.swift b/ImageViewer/ImageViewerController.swift deleted file mode 100644 index e3d90fd..0000000 --- a/ImageViewer/ImageViewerController.swift +++ /dev/null @@ -1,145 +0,0 @@ -import UIKit -import AVFoundation - -public final class ImageViewerController: UIViewController { - @IBOutlet fileprivate var scrollView: UIScrollView! - @IBOutlet fileprivate var imageView: UIImageView! - @IBOutlet fileprivate var activityIndicator: UIActivityIndicatorView! - - fileprivate var transitionHandler: ImageViewerTransitioningHandler? - fileprivate let configuration: ImageViewerConfiguration? - - public override var prefersStatusBarHidden: Bool { - return true - } - - public init(configuration: ImageViewerConfiguration?) { - self.configuration = configuration - super.init(nibName: String(describing: type(of: self)), bundle: Bundle(for: type(of: self))) - - modalPresentationStyle = .overFullScreen - modalTransitionStyle = .crossDissolve - modalPresentationCapturesStatusBarAppearance = true - } - - required public init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override public func viewDidLoad() { - super.viewDidLoad() - imageView.image = configuration?.imageView?.image ?? configuration?.image - - setupScrollView() - setupGestureRecognizers() - setupTransitions() - setupActivityIndicator() - } -} - -extension ImageViewerController: UIScrollViewDelegate { - public func viewForZooming(in scrollView: UIScrollView) -> UIView? { - return imageView - } - - public func scrollViewDidZoom(_ scrollView: UIScrollView) { - guard let image = imageView.image else { return } - let imageViewSize = Utilities.aspectFitRect(forSize: image.size, insideRect: imageView.frame) - let verticalInsets = -(scrollView.contentSize.height - max(imageViewSize.height, scrollView.bounds.height)) / 2 - let horizontalInsets = -(scrollView.contentSize.width - max(imageViewSize.width, scrollView.bounds.width)) / 2 - scrollView.contentInset = UIEdgeInsets(top: verticalInsets, left: horizontalInsets, bottom: verticalInsets, right: horizontalInsets) - } -} - -extension ImageViewerController: UIGestureRecognizerDelegate { - public func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { - return scrollView.zoomScale == scrollView.minimumZoomScale - } -} - -private extension ImageViewerController { - func setupScrollView() { - scrollView.decelerationRate = UIScrollViewDecelerationRateFast - scrollView.alwaysBounceVertical = true - scrollView.alwaysBounceHorizontal = true - } - - func setupGestureRecognizers() { - let tapGestureRecognizer = UITapGestureRecognizer() - tapGestureRecognizer.numberOfTapsRequired = 2 - tapGestureRecognizer.addTarget(self, action: #selector(imageViewDoubleTapped)) - imageView.addGestureRecognizer(tapGestureRecognizer) - - let panGestureRecognizer = UIPanGestureRecognizer() - panGestureRecognizer.addTarget(self, action: #selector(imageViewPanned(_:))) - panGestureRecognizer.delegate = self - imageView.addGestureRecognizer(panGestureRecognizer) - } - - func setupTransitions() { - guard let imageView = configuration?.imageView else { return } - transitionHandler = ImageViewerTransitioningHandler(fromImageView: imageView, toImageView: self.imageView) - transitioningDelegate = transitionHandler - } - - func setupActivityIndicator() { - guard let block = configuration?.imageBlock else { return } - activityIndicator.startAnimating() - block { [weak self] image in - guard let image = image else { return } - DispatchQueue.main.async { - self?.activityIndicator.stopAnimating() - self?.imageView.image = image - } - } - } - - @IBAction func closeButtonPressed() { - dismiss(animated: true) - } - - @objc func imageViewDoubleTapped(recognizer: UITapGestureRecognizer) { - func zoomRectForScale(scale: CGFloat, center: CGPoint) -> CGRect { - var zoomRect = CGRect.zero - zoomRect.size.height = imageView.frame.size.height / scale - zoomRect.size.width = imageView.frame.size.width / scale - let newCenter = scrollView.convert(center, from: imageView) - zoomRect.origin.x = newCenter.x - (zoomRect.size.width / 2.0) - zoomRect.origin.y = newCenter.y - (zoomRect.size.height / 2.0) - return zoomRect - } - - if scrollView.zoomScale > scrollView.minimumZoomScale { - scrollView.setZoomScale(scrollView.minimumZoomScale, animated: true) - } else { - scrollView.zoom(to: zoomRectForScale(scale: scrollView.maximumZoomScale, center: recognizer.location(in: recognizer.view)), animated: true) - } - } - - @objc func imageViewPanned(_ recognizer: UIPanGestureRecognizer) { - guard transitionHandler != nil else { return } - - let translation = recognizer.translation(in: imageView) - let velocity = recognizer.velocity(in: imageView) - - switch recognizer.state { - case .began: - transitionHandler?.dismissInteractively = true - dismiss(animated: true) - case .changed: - let percentage = abs(translation.y) / imageView.bounds.height - transitionHandler?.dismissalInteractor.update(percentage: percentage) - transitionHandler?.dismissalInteractor.update(transform: CGAffineTransform(translationX: translation.x, y: translation.y)) - case .ended, .cancelled: - transitionHandler?.dismissInteractively = false - let percentage = abs(translation.y + velocity.y) / imageView.bounds.height - if percentage > 0.25 { - transitionHandler?.dismissalInteractor.finish() - } else { - transitionHandler?.dismissalInteractor.cancel() - } - default: break - } - } -} - diff --git a/ImageViewer/Info.plist b/ImageViewer/Info.plist index a246732..fd7f1ff 100644 --- a/ImageViewer/Info.plist +++ b/ImageViewer/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 1.1.1 + 2.0 CFBundleVersion $(CURRENT_PROJECT_VERSION) NSPrincipalClass diff --git a/ImageViewer/Transitions/ImageViewerDismissalInteractor.swift b/ImageViewer/Transitions/ImageViewerDismissalInteractor.swift index caf49a3..4741567 100644 --- a/ImageViewer/Transitions/ImageViewerDismissalInteractor.swift +++ b/ImageViewer/Transitions/ImageViewerDismissalInteractor.swift @@ -1,7 +1,7 @@ import UIKit final class ImageViewerDismissalInteractor: NSObject, UIViewControllerInteractiveTransitioning { - fileprivate let transition: ImageViewerDismissalTransition + private let transition: ImageViewerDismissalTransition init(transition: ImageViewerDismissalTransition) { self.transition = transition diff --git a/ImageViewer/Transitions/ImageViewerDismissalTransition.swift b/ImageViewer/Transitions/ImageViewerDismissalTransition.swift index 545c72f..c470989 100644 --- a/ImageViewer/Transitions/ImageViewerDismissalTransition.swift +++ b/ImageViewer/Transitions/ImageViewerDismissalTransition.swift @@ -1,30 +1,31 @@ import UIKit final class ImageViewerDismissalTransition: NSObject, UIViewControllerAnimatedTransitioning { - fileprivate weak var transitionContext: UIViewControllerContextTransitioning? + private weak var transitionContext: UIViewControllerContextTransitioning? - fileprivate let fromImageView: UIImageView - fileprivate var toImageView: UIImageView + private let fromAssetView: UIView + private var toImageView: UIImageView + private var dismissImage: UIImage? - fileprivate var animatableImageview = AnimatableImageView() - fileprivate var fromView: UIView? - fileprivate var fadeView = UIView() + private var animatableImageview = AnimatableImageView() + private var fromView: UIView? + private var fadeView = UIView() enum TransitionState { case start case end } - fileprivate var translationTransform: CGAffineTransform = CGAffineTransform.identity { + private var translationTransform: CGAffineTransform = CGAffineTransform.identity { didSet { updateTransform() } } - fileprivate var scaleTransform: CGAffineTransform = CGAffineTransform.identity { + private var scaleTransform: CGAffineTransform = CGAffineTransform.identity { didSet { updateTransform() } } - init(fromImageView: UIImageView, toImageView: UIImageView) { - self.fromImageView = fromImageView + init(fromAssetView: UIView, toImageView: UIImageView) { + self.fromAssetView = fromAssetView self.toImageView = toImageView super.init() } @@ -39,6 +40,10 @@ final class ImageViewerDismissalTransition: NSObject, UIViewControllerAnimatedTr scaleTransform = CGAffineTransform(scaleX: invertedPercentage, y: invertedPercentage) } + func update(dismissImage: UIImage?) { + self.dismissImage = dismissImage + } + func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { return 0.3 } @@ -52,11 +57,12 @@ final class ImageViewerDismissalTransition: NSObject, UIViewControllerAnimatedTr self.transitionContext = transitionContext self.fromView = transitionContext.view(forKey: UITransitionContextViewKey.from)! let containerView = transitionContext.containerView - let fromSuperView = fromImageView.superview! - let image = fromImageView.image ?? toImageView.image + let fromSuperView = fromAssetView.superview! + + let image = dismissImage ?? toImageView.image animatableImageview.image = image - animatableImageview.frame = fromSuperView.convert(fromImageView.frame, to: nil) + animatableImageview.frame = fromSuperView.convert(fromAssetView.frame, to: nil) animatableImageview.contentMode = .scaleAspectFit fromView?.isHidden = true @@ -107,7 +113,7 @@ private extension ImageViewerDismissalTransition { case .start: self.animatableImageview.contentMode = .scaleAspectFit self.animatableImageview.transform = .identity - self.animatableImageview.frame = self.fromImageView.frame + self.animatableImageview.frame = self.fromAssetView.frame self.fadeView.alpha = 1.0 case .end: self.animatableImageview.contentMode = self.toImageView.contentMode diff --git a/ImageViewer/Transitions/ImageViewerTransitioningHandler.swift b/ImageViewer/Transitions/ImageViewerTransitioningHandler.swift index 32c6c72..8e26783 100644 --- a/ImageViewer/Transitions/ImageViewerTransitioningHandler.swift +++ b/ImageViewer/Transitions/ImageViewerTransitioningHandler.swift @@ -1,19 +1,23 @@ import UIKit final class ImageViewerTransitioningHandler: NSObject { - fileprivate let presentationTransition: ImageViewerPresentationTransition - fileprivate let dismissalTransition: ImageViewerDismissalTransition + private let presentationTransition: ImageViewerPresentationTransition + private let dismissalTransition: ImageViewerDismissalTransition let dismissalInteractor: ImageViewerDismissalInteractor var dismissInteractively = false - init(fromImageView: UIImageView, toImageView: UIImageView) { + init(fromImageView: UIImageView, toAssetView: UIView) { self.presentationTransition = ImageViewerPresentationTransition(fromImageView: fromImageView) - self.dismissalTransition = ImageViewerDismissalTransition(fromImageView: toImageView, toImageView: fromImageView) + self.dismissalTransition = ImageViewerDismissalTransition(fromAssetView: toAssetView, toImageView: fromImageView) self.dismissalInteractor = ImageViewerDismissalInteractor(transition: dismissalTransition) super.init() } + + func update(dismissImage: UIImage?) { + dismissalTransition.update(dismissImage: dismissImage) + } } extension ImageViewerTransitioningHandler: UIViewControllerTransitioningDelegate { diff --git a/ImageViewer/Utilities.swift b/ImageViewer/Utilities.swift index 92a11f8..7b65e3f 100644 --- a/ImageViewer/Utilities.swift +++ b/ImageViewer/Utilities.swift @@ -1,7 +1,35 @@ import QuartzCore import AVFoundation +import UIKit +import VideoToolbox + +public struct Utilities { + public static func screenShot(fromAsset asset: AVAsset, atTime time: CMTime, size: CGSize) -> UIImage? { + let imageGenerator = AVAssetImageGenerator(asset: asset) + imageGenerator.requestedTimeToleranceBefore = kCMTimeZero + imageGenerator.requestedTimeToleranceAfter = kCMTimeZero + imageGenerator.appliesPreferredTrackTransform = true + imageGenerator.maximumSize = size + guard let image = try? imageGenerator.copyCGImage(at: time, actualTime: nil) else { return nil } + return UIImage(cgImage: image) + } + + static func image(fromPixelBuffer pixelBuffer: CVPixelBuffer) -> UIImage? { + var coreGraphicsImage: CGImage? + VTCreateCGImageFromCVPixelBuffer(pixelBuffer, nil, &coreGraphicsImage) + guard let cgImage = coreGraphicsImage else { return nil } + return UIImage(cgImage: cgImage) + } + + static func zoomRect(forScale scale: CGFloat, center: CGPoint, inRect: CGRect) -> CGRect { + var zoomRect = CGRect.zero + zoomRect.size.height = inRect.size.height / scale + zoomRect.size.width = inRect.size.width / scale + zoomRect.origin.x = center.x - (zoomRect.size.width / 2.0) + zoomRect.origin.y = center.y - (zoomRect.size.height / 2.0) + return zoomRect + } -struct Utilities { static func rect(forSize size: CGSize) -> CGRect { return CGRect(origin: .zero, size: size) } diff --git a/SimpleImageViewer.xcodeproj/project.pbxproj b/SimpleImageViewer.xcodeproj/project.pbxproj index dbe15e0..0beabf9 100644 --- a/SimpleImageViewer.xcodeproj/project.pbxproj +++ b/SimpleImageViewer.xcodeproj/project.pbxproj @@ -12,7 +12,21 @@ A02EE5D71EF1897D000EAB41 /* UtilitiesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A02EE5D61EF1897D000EAB41 /* UtilitiesTests.swift */; }; A02EE5D91EF1897D000EAB41 /* SimpleImageViewer.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A0BF70A71EDC5B1400109F6E /* SimpleImageViewer.framework */; }; A02EE5DF1EF189B4000EAB41 /* Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = A02EE5CD1EF172C3000EAB41 /* Utilities.swift */; }; - A05E9FA51EF7FB640024CF47 /* ImageViewerConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = A05E9FA41EF7FB640024CF47 /* ImageViewerConfiguration.swift */; }; + A03B7BF5214402B100F855CF /* VideoCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = A03B7BF4214402B100F855CF /* VideoCell.swift */; }; + A03B7BF82144144200F855CF /* AssetViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A03B7BF72144144200F855CF /* AssetViewController.swift */; }; + A03B7BFE2144671A00F855CF /* 2.mp4 in Resources */ = {isa = PBXBuildFile; fileRef = A03B7BF92144656500F855CF /* 2.mp4 */; }; + A03B7BFF2144671A00F855CF /* 3.mp4 in Resources */ = {isa = PBXBuildFile; fileRef = A03B7BFA2144656B00F855CF /* 3.mp4 */; }; + A03B7C002144671A00F855CF /* 4.mp4 in Resources */ = {isa = PBXBuildFile; fileRef = A03B7BFB2144657000F855CF /* 4.mp4 */; }; + A03B7C012144671A00F855CF /* 5.mp4 in Resources */ = {isa = PBXBuildFile; fileRef = A03B7BFC2144664400F855CF /* 5.mp4 */; }; + A03B7C022144671A00F855CF /* 6.mp4 in Resources */ = {isa = PBXBuildFile; fileRef = A03B7BFD214466EC00F855CF /* 6.mp4 */; }; + A03C418720E52DD50095925B /* 1.mp4 in Resources */ = {isa = PBXBuildFile; fileRef = A03C418620E52DD50095925B /* 1.mp4 */; }; + A0503D27205AE28F00AFF8E5 /* GalleryViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A0503D23205AE28F00AFF8E5 /* GalleryViewController.swift */; }; + A0503D28205AE28F00AFF8E5 /* GalleryViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = A0503D24205AE28F00AFF8E5 /* GalleryViewController.xib */; }; + A0503D29205AE28F00AFF8E5 /* ImageViewerConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = A0503D25205AE28F00AFF8E5 /* ImageViewerConfiguration.swift */; }; + A0503D2A205AE28F00AFF8E5 /* ImageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A0503D26205AE28F00AFF8E5 /* ImageViewController.swift */; }; + A0503D2C205AE30400AFF8E5 /* ImageViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = A0503D2B205AE30400AFF8E5 /* ImageViewController.xib */; }; + A0503D2F205AF00400AFF8E5 /* VideoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A0503D2E205AF00400AFF8E5 /* VideoViewController.swift */; }; + A0503D31205AF02100AFF8E5 /* VideoViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = A0503D30205AF02100AFF8E5 /* VideoViewController.xib */; }; A07E76D71ECC94A400B77D46 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = A07E76D61ECC94A400B77D46 /* AppDelegate.swift */; }; A07E76D91ECC94A400B77D46 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A07E76D81ECC94A400B77D46 /* ViewController.swift */; }; A07E76DE1ECC94A400B77D46 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A07E76DD1ECC94A400B77D46 /* Assets.xcassets */; }; @@ -24,10 +38,8 @@ A0BF70B41EDC758300109F6E /* ImageViewerTransitioningHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = A07E771B1ECE313E00B77D46 /* ImageViewerTransitioningHandler.swift */; }; A0BF70B51EDC758300109F6E /* ImageViewerPresentationTransition.swift in Sources */ = {isa = PBXBuildFile; fileRef = A07E77191ECE2EDF00B77D46 /* ImageViewerPresentationTransition.swift */; }; A0BF70B61EDC758300109F6E /* ImageViewerDismissalTransition.swift in Sources */ = {isa = PBXBuildFile; fileRef = A07E77251ECF3E6700B77D46 /* ImageViewerDismissalTransition.swift */; }; - A0BF70B71EDC758300109F6E /* ImageViewerController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A07E76EB1ECC94EA00B77D46 /* ImageViewerController.swift */; }; A0BF70B81EDC758300109F6E /* AnimatableImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A07E771F1ECE390C00B77D46 /* AnimatableImageView.swift */; }; A0BF70B91EDC759A00109F6E /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = A07E76DA1ECC94A400B77D46 /* Main.storyboard */; }; - A0BF70BA1EDC75AE00109F6E /* ImageViewerController.xib in Resources */ = {isa = PBXBuildFile; fileRef = A07E76ED1ECC959700B77D46 /* ImageViewerController.xib */; }; A0BF70BB1EDC75AE00109F6E /* ImageViewer-Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A07E77231ECF289800B77D46 /* ImageViewer-Assets.xcassets */; }; A0BF70BC1EDC767B00109F6E /* HeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A07E77211ECF1FC100B77D46 /* HeaderView.swift */; }; /* End PBXBuildFile section */ @@ -63,15 +75,27 @@ A02EE5D41EF1897D000EAB41 /* SimpleImageViewerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SimpleImageViewerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; A02EE5D61EF1897D000EAB41 /* UtilitiesTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UtilitiesTests.swift; sourceTree = ""; }; A02EE5D81EF1897D000EAB41 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + A03B7BF4214402B100F855CF /* VideoCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoCell.swift; sourceTree = ""; }; + A03B7BF72144144200F855CF /* AssetViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AssetViewController.swift; sourceTree = ""; }; + A03B7BF92144656500F855CF /* 2.mp4 */ = {isa = PBXFileReference; lastKnownFileType = file; path = 2.mp4; sourceTree = ""; }; + A03B7BFA2144656B00F855CF /* 3.mp4 */ = {isa = PBXFileReference; lastKnownFileType = file; path = 3.mp4; sourceTree = ""; }; + A03B7BFB2144657000F855CF /* 4.mp4 */ = {isa = PBXFileReference; lastKnownFileType = file; path = 4.mp4; sourceTree = ""; }; + A03B7BFC2144664400F855CF /* 5.mp4 */ = {isa = PBXFileReference; lastKnownFileType = file; path = 5.mp4; sourceTree = ""; }; + A03B7BFD214466EC00F855CF /* 6.mp4 */ = {isa = PBXFileReference; lastKnownFileType = file; path = 6.mp4; sourceTree = ""; }; + A03C418620E52DD50095925B /* 1.mp4 */ = {isa = PBXFileReference; lastKnownFileType = file; path = 1.mp4; sourceTree = ""; }; A03DAD4E1EFCFF030060DF69 /* Package.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Package.swift; sourceTree = SOURCE_ROOT; }; - A05E9FA41EF7FB640024CF47 /* ImageViewerConfiguration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageViewerConfiguration.swift; sourceTree = ""; }; + A0503D23205AE28F00AFF8E5 /* GalleryViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GalleryViewController.swift; sourceTree = ""; }; + A0503D24205AE28F00AFF8E5 /* GalleryViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = GalleryViewController.xib; sourceTree = ""; }; + A0503D25205AE28F00AFF8E5 /* ImageViewerConfiguration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageViewerConfiguration.swift; sourceTree = ""; }; + A0503D26205AE28F00AFF8E5 /* ImageViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageViewController.swift; sourceTree = ""; }; + A0503D2B205AE30400AFF8E5 /* ImageViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = ImageViewController.xib; sourceTree = ""; }; + A0503D2E205AF00400AFF8E5 /* VideoViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoViewController.swift; sourceTree = ""; }; + A0503D30205AF02100AFF8E5 /* VideoViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = VideoViewController.xib; sourceTree = ""; }; A07E76D31ECC94A400B77D46 /* Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Example.app; sourceTree = BUILT_PRODUCTS_DIR; }; A07E76D61ECC94A400B77D46 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; A07E76D81ECC94A400B77D46 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; A07E76DB1ECC94A400B77D46 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; A07E76DD1ECC94A400B77D46 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; - A07E76EB1ECC94EA00B77D46 /* ImageViewerController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageViewerController.swift; sourceTree = ""; }; - A07E76ED1ECC959700B77D46 /* ImageViewerController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; name = ImageViewerController.xib; path = Resources/ImageViewerController.xib; sourceTree = ""; }; A07E77161ECE0F3500B77D46 /* ImageCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageCell.swift; sourceTree = ""; }; A07E77191ECE2EDF00B77D46 /* ImageViewerPresentationTransition.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageViewerPresentationTransition.swift; sourceTree = ""; }; A07E771B1ECE313E00B77D46 /* ImageViewerTransitioningHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageViewerTransitioningHandler.swift; sourceTree = ""; }; @@ -123,6 +147,12 @@ A03518701EDD6C6700AAE484 /* Resources */ = { isa = PBXGroup; children = ( + A03C418620E52DD50095925B /* 1.mp4 */, + A03B7BF92144656500F855CF /* 2.mp4 */, + A03B7BFA2144656B00F855CF /* 3.mp4 */, + A03B7BFB2144657000F855CF /* 4.mp4 */, + A03B7BFC2144664400F855CF /* 5.mp4 */, + A03B7BFD214466EC00F855CF /* 6.mp4 */, A07E76DD1ECC94A400B77D46 /* Assets.xcassets */, A07E76DA1ECC94A400B77D46 /* Main.storyboard */, ); @@ -137,6 +167,34 @@ path = Resources; sourceTree = ""; }; + A03B7BF62144141F00F855CF /* AssetViewController */ = { + isa = PBXGroup; + children = ( + A03B7BF72144144200F855CF /* AssetViewController.swift */, + A0503D22205AE28F00AFF8E5 /* ImageViewerController */, + A0503D2D205AEFEB00AFF8E5 /* VideoViewerController */, + ); + path = AssetViewController; + sourceTree = ""; + }; + A0503D22205AE28F00AFF8E5 /* ImageViewerController */ = { + isa = PBXGroup; + children = ( + A0503D26205AE28F00AFF8E5 /* ImageViewController.swift */, + A0503D2B205AE30400AFF8E5 /* ImageViewController.xib */, + ); + path = ImageViewerController; + sourceTree = ""; + }; + A0503D2D205AEFEB00AFF8E5 /* VideoViewerController */ = { + isa = PBXGroup; + children = ( + A0503D2E205AF00400AFF8E5 /* VideoViewController.swift */, + A0503D30205AF02100AFF8E5 /* VideoViewController.xib */, + ); + path = VideoViewerController; + sourceTree = ""; + }; A07E76CA1ECC94A400B77D46 = { isa = PBXGroup; children = ( @@ -166,6 +224,7 @@ A07E76D61ECC94A400B77D46 /* AppDelegate.swift */, A07E77211ECF1FC100B77D46 /* HeaderView.swift */, A07E77161ECE0F3500B77D46 /* ImageCell.swift */, + A03B7BF4214402B100F855CF /* VideoCell.swift */, A07E76D81ECC94A400B77D46 /* ViewController.swift */, ); path = Example; @@ -182,24 +241,17 @@ path = Transitions; sourceTree = ""; }; - A08B781E1EF16CCF00F8C5BC /* ImageViewerController */ = { - isa = PBXGroup; - children = ( - A05E9FA41EF7FB640024CF47 /* ImageViewerConfiguration.swift */, - A07E76EB1ECC94EA00B77D46 /* ImageViewerController.swift */, - A07E76ED1ECC959700B77D46 /* ImageViewerController.xib */, - ); - name = ImageViewerController; - sourceTree = ""; - }; A0BF70A81EDC5B1400109F6E /* SimpleImageViewer */ = { isa = PBXGroup; children = ( + A0503D25205AE28F00AFF8E5 /* ImageViewerConfiguration.swift */, + A0503D23205AE28F00AFF8E5 /* GalleryViewController.swift */, + A0503D24205AE28F00AFF8E5 /* GalleryViewController.xib */, + A03B7BF62144141F00F855CF /* AssetViewController */, A0BF70A91EDC5B1400109F6E /* ImageViewer.h */, A02EE5CB1EF16EBE000EAB41 /* Info.plist */, A07E771F1ECE390C00B77D46 /* AnimatableImageView.swift */, A02EE5CD1EF172C3000EAB41 /* Utilities.swift */, - A08B781E1EF16CCF00F8C5BC /* ImageViewerController */, A07E77181ECE2EC800B77D46 /* Transitions */, A03518711EDD6C7D00AAE484 /* Resources */, ); @@ -335,8 +387,14 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + A03B7C022144671A00F855CF /* 6.mp4 in Resources */, + A03B7BFE2144671A00F855CF /* 2.mp4 in Resources */, + A03B7C012144671A00F855CF /* 5.mp4 in Resources */, A02EE5B91EF16E54000EAB41 /* Info.plist in Resources */, + A03B7C002144671A00F855CF /* 4.mp4 in Resources */, A0BF70B91EDC759A00109F6E /* Main.storyboard in Resources */, + A03B7BFF2144671A00F855CF /* 3.mp4 in Resources */, + A03C418720E52DD50095925B /* 1.mp4 in Resources */, A07E76DE1ECC94A400B77D46 /* Assets.xcassets in Resources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -345,8 +403,10 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - A0BF70BA1EDC75AE00109F6E /* ImageViewerController.xib in Resources */, A0BF70BB1EDC75AE00109F6E /* ImageViewer-Assets.xcassets in Resources */, + A0503D2C205AE30400AFF8E5 /* ImageViewController.xib in Resources */, + A0503D28205AE28F00AFF8E5 /* GalleryViewController.xib in Resources */, + A0503D31205AF02100AFF8E5 /* VideoViewController.xib in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -366,6 +426,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + A03B7BF5214402B100F855CF /* VideoCell.swift in Sources */, A07E76D91ECC94A400B77D46 /* ViewController.swift in Sources */, A07E77171ECE0F3500B77D46 /* ImageCell.swift in Sources */, A07E76D71ECC94A400B77D46 /* AppDelegate.swift in Sources */, @@ -378,13 +439,16 @@ buildActionMask = 2147483647; files = ( A0BF70B41EDC758300109F6E /* ImageViewerTransitioningHandler.swift in Sources */, + A0503D2A205AE28F00AFF8E5 /* ImageViewController.swift in Sources */, A0BF70B51EDC758300109F6E /* ImageViewerPresentationTransition.swift in Sources */, A02EE5CF1EF172C8000EAB41 /* Utilities.swift in Sources */, + A0503D2F205AF00400AFF8E5 /* VideoViewController.swift in Sources */, + A03B7BF82144144200F855CF /* AssetViewController.swift in Sources */, A0BF70B61EDC758300109F6E /* ImageViewerDismissalTransition.swift in Sources */, - A0BF70B71EDC758300109F6E /* ImageViewerController.swift in Sources */, A08B781D1EF1684600F8C5BC /* ImageViewerDismissalInteractor.swift in Sources */, - A05E9FA51EF7FB640024CF47 /* ImageViewerConfiguration.swift in Sources */, + A0503D29205AE28F00AFF8E5 /* ImageViewerConfiguration.swift in Sources */, A0BF70B81EDC758300109F6E /* AnimatableImageView.swift in Sources */, + A0503D27205AE28F00AFF8E5 /* GalleryViewController.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -547,15 +611,16 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; DEVELOPMENT_TEAM = 44MAY72KU6; INFOPLIST_FILE = "$(SRCROOT)/ImageViewer/Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - PRODUCT_BUNDLE_IDENTIFIER = com.afrogleapbv.inhouse.swift.imageviewer; + PRODUCT_BUNDLE_IDENTIFIER = com.afrogleapbv.debug.swift.imageviewer; PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE = "d5b8f7cb-0120-427a-9ff1-14d28ee0046f"; - PROVISIONING_PROFILE_SPECIFIER = "aFrogleap Wildcard Development"; + PROVISIONING_PROFILE = "9ab3abbb-84d3-4f6f-b80c-41e5a4358e0a"; + PROVISIONING_PROFILE_SPECIFIER = "match Development com.afrogleapbv.debug.*"; SWIFT_SWIFT3_OBJC_INFERENCE = Default; SWIFT_VERSION = 4.0; }; @@ -568,9 +633,9 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; DEVELOPMENT_TEAM = ""; INFOPLIST_FILE = "$(SRCROOT)/ImageViewer/Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - PRODUCT_BUNDLE_IDENTIFIER = com.afrogleapbv.inhouse.swift.imageviewer; + PRODUCT_BUNDLE_IDENTIFIER = com.afrogleapbv.debug.swift.imageviewer; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_SWIFT3_OBJC_INFERENCE = Default; @@ -590,7 +655,7 @@ DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = "$(SRCROOT)/ImageViewer/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.afrogleap.ImageViewer; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -615,7 +680,7 @@ DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = "$(SRCROOT)/ImageViewer/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.afrogleap.ImageViewer; PRODUCT_NAME = "$(TARGET_NAME)"; diff --git a/SimpleImageViewer.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/SimpleImageViewer.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/SimpleImageViewer.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/SimpleImageViewer.xcodeproj/project.xcworkspace/xcuserdata/lucas.xcuserdatad/UserInterfaceState.xcuserstate b/SimpleImageViewer.xcodeproj/project.xcworkspace/xcuserdata/lucas.xcuserdatad/UserInterfaceState.xcuserstate index 0d01038..6683cc6 100644 Binary files a/SimpleImageViewer.xcodeproj/project.xcworkspace/xcuserdata/lucas.xcuserdatad/UserInterfaceState.xcuserstate and b/SimpleImageViewer.xcodeproj/project.xcworkspace/xcuserdata/lucas.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/SimpleImageViewer.xcodeproj/xcshareddata/xcschemes/SimpleImageViewer.xcscheme b/SimpleImageViewer.xcodeproj/xcshareddata/xcschemes/SimpleImageViewer.xcscheme index bf75f27..4e8b452 100644 --- a/SimpleImageViewer.xcodeproj/xcshareddata/xcschemes/SimpleImageViewer.xcscheme +++ b/SimpleImageViewer.xcodeproj/xcshareddata/xcschemes/SimpleImageViewer.xcscheme @@ -1,6 +1,6 @@ @@ -36,6 +37,7 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + language = "" launchStyle = "0" useCustomWorkingDirectory = "NO" ignoresPersistentStateOnLaunch = "NO" diff --git a/SimpleImageViewer.xcodeproj/xcuserdata/lucas.xcuserdatad/xcschemes/Example.xcscheme b/SimpleImageViewer.xcodeproj/xcuserdata/lucas.xcuserdatad/xcschemes/Example.xcscheme index efc3975..640b69e 100644 --- a/SimpleImageViewer.xcodeproj/xcuserdata/lucas.xcuserdatad/xcschemes/Example.xcscheme +++ b/SimpleImageViewer.xcodeproj/xcuserdata/lucas.xcuserdatad/xcschemes/Example.xcscheme @@ -1,6 +1,6 @@ @@ -45,6 +46,7 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + language = "" launchStyle = "0" useCustomWorkingDirectory = "NO" ignoresPersistentStateOnLaunch = "NO" diff --git a/SimpleImageViewer.xcodeproj/xcuserdata/lucas.xcuserdatad/xcschemes/SimpleImageViewerTests.xcscheme b/SimpleImageViewer.xcodeproj/xcuserdata/lucas.xcuserdatad/xcschemes/SimpleImageViewerTests.xcscheme index bad6b69..7a8772c 100644 --- a/SimpleImageViewer.xcodeproj/xcuserdata/lucas.xcuserdatad/xcschemes/SimpleImageViewerTests.xcscheme +++ b/SimpleImageViewer.xcodeproj/xcuserdata/lucas.xcuserdatad/xcschemes/SimpleImageViewerTests.xcscheme @@ -1,6 +1,6 @@