diff --git a/Cartfile b/Cartfile index aa7aba1..e66c656 100644 --- a/Cartfile +++ b/Cartfile @@ -1,3 +1 @@ -github "onevcat/Kingfisher" -github "Quick/Nimble" -github "Quick/Quick" \ No newline at end of file +github "onevcat/Kingfisher" ~> 4.0.0 \ No newline at end of file diff --git a/Cartfile.private b/Cartfile.private new file mode 100644 index 0000000..e03b90c --- /dev/null +++ b/Cartfile.private @@ -0,0 +1,2 @@ +github "Quick/Nimble" ~> 7.3.0 +github "Quick/Quick" ~> 1.3.0 \ No newline at end of file diff --git a/PhotoBrowser.xcodeproj/project.pbxproj b/PhotoBrowser.xcodeproj/project.pbxproj index 1d42513..0f41937 100644 --- a/PhotoBrowser.xcodeproj/project.pbxproj +++ b/PhotoBrowser.xcodeproj/project.pbxproj @@ -14,6 +14,9 @@ 4A52D2301C72F47C001C257B /* Photo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A52D22F1C72F47C001C257B /* Photo.swift */; }; 4A52D2381C72F568001C257B /* PhotoPreviewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A52D2371C72F568001C257B /* PhotoPreviewController.swift */; }; 4A6BC7C11C770F6400DACDA5 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4A6BC7C01C770F6400DACDA5 /* Images.xcassets */; }; + 4A70A003225308A100DBD070 /* Kingfisher+CacheSerializer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A70A002225308A100DBD070 /* Kingfisher+CacheSerializer.swift */; }; + 4A7E8F3E224A16A6005B54C8 /* ImageContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A7E8F3D224A16A6005B54C8 /* ImageContainerView.swift */; }; + 4A8A90962244C27D00A09C55 /* HighResolutionImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A8A90952244C27D00A09C55 /* HighResolutionImageView.swift */; }; 4A8EB7291CEF16020065EAB0 /* PBActionBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A8EB7281CEF16020065EAB0 /* PBActionBarItem.swift */; }; 864F45E81E9F0A4900FF9215 /* Skitch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 864F45E71E9F0A4900FF9215 /* Skitch.swift */; }; 86AEF7301EDEC8A000034DBA /* SkitchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 86AEF72F1EDEC8A000034DBA /* SkitchView.swift */; }; @@ -60,6 +63,9 @@ 4A52D22F1C72F47C001C257B /* Photo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Photo.swift; sourceTree = ""; }; 4A52D2371C72F568001C257B /* PhotoPreviewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PhotoPreviewController.swift; sourceTree = ""; }; 4A6BC7C01C770F6400DACDA5 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; + 4A70A002225308A100DBD070 /* Kingfisher+CacheSerializer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Kingfisher+CacheSerializer.swift"; sourceTree = ""; }; + 4A7E8F3D224A16A6005B54C8 /* ImageContainerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageContainerView.swift; sourceTree = ""; }; + 4A8A90952244C27D00A09C55 /* HighResolutionImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HighResolutionImageView.swift; sourceTree = ""; }; 4A8EB7281CEF16020065EAB0 /* PBActionBarItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PBActionBarItem.swift; sourceTree = ""; }; 864F45E71E9F0A4900FF9215 /* Skitch.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Skitch.swift; sourceTree = ""; }; 86AEF72F1EDEC8A000034DBA /* SkitchView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SkitchView.swift; sourceTree = ""; }; @@ -155,6 +161,8 @@ C4B64B4E2117E75500B29C5B /* PBToolbar.swift */, C4B64B502117E77200B29C5B /* PBNavigationBar.swift */, C4B64B522117E7B000B29C5B /* WaitingView.swift */, + 4A8A90952244C27D00A09C55 /* HighResolutionImageView.swift */, + 4A7E8F3D224A16A6005B54C8 /* ImageContainerView.swift */, ); path = CustomView; sourceTree = ""; @@ -173,6 +181,7 @@ children = ( C4C79EA02057808200C92C0C /* UIView+Frame.swift */, C4B64B622117E95300B29C5B /* UIViewController+Animation.swift */, + 4A70A002225308A100DBD070 /* Kingfisher+CacheSerializer.swift */, ); path = Extensions; sourceTree = ""; @@ -433,6 +442,7 @@ C4B64B4D2117E73100B29C5B /* GradientView.swift in Sources */, 4A52D2301C72F47C001C257B /* Photo.swift in Sources */, C4B64B5B2117E8D100B29C5B /* DismissImmediatelyAnimation.swift in Sources */, + 4A70A003225308A100DBD070 /* Kingfisher+CacheSerializer.swift in Sources */, C4B64B632117E95300B29C5B /* UIViewController+Animation.swift in Sources */, C4B64B5F2117E91300B29C5B /* PresentAnimation.swift in Sources */, 4A52D1FE1C72CE69001C257B /* PhotoBrowser.swift in Sources */, @@ -441,6 +451,8 @@ C4B64B4F2117E75500B29C5B /* PBToolbar.swift in Sources */, C4C79EA12057808200C92C0C /* UIView+Frame.swift in Sources */, C4B64B532117E7B000B29C5B /* WaitingView.swift in Sources */, + 4A8A90962244C27D00A09C55 /* HighResolutionImageView.swift in Sources */, + 4A7E8F3E224A16A6005B54C8 /* ImageContainerView.swift in Sources */, 4A8EB7291CEF16020065EAB0 /* PBActionBarItem.swift in Sources */, C4B64B5D2117E90000B29C5B /* DismissAnimation.swift in Sources */, C4B64B652117EA6300B29C5B /* PBConstant.swift in Sources */, diff --git a/PhotoBrowser.xcworkspace/xcuserdata/wangwei.xcuserdatad/UserInterfaceState.xcuserstate b/PhotoBrowser.xcworkspace/xcuserdata/wangwei.xcuserdatad/UserInterfaceState.xcuserstate deleted file mode 100644 index 8fc7e11..0000000 Binary files a/PhotoBrowser.xcworkspace/xcuserdata/wangwei.xcuserdatad/UserInterfaceState.xcuserstate and /dev/null differ diff --git a/PhotoBrowser/Animation/DismissAnimation.swift b/PhotoBrowser/Animation/DismissAnimation.swift index 23b8d9b..fb74c6d 100644 --- a/PhotoBrowser/Animation/DismissAnimation.swift +++ b/PhotoBrowser/Animation/DismissAnimation.swift @@ -35,10 +35,12 @@ open class DismissAnimation: NSObject, UIViewControllerAnimatedTransitioning { toView.alpha = 0 UIView.animate(withDuration: PBConstant.Animation.dismissDuration, delay: 0, options: .curveEaseOut, animations: { + // this transform may cause memory issue fromViewController.view.transform = scale.concatenating(translate) fromViewController.view.alpha = 0 self.toView.alpha = 1 }) { (_) in + fromViewController.view.removeFromSuperview() transitionContext.completeTransition(!transitionContext.transitionWasCancelled) } } diff --git a/PhotoBrowser/CustomView/HighResolutionImageView.swift b/PhotoBrowser/CustomView/HighResolutionImageView.swift new file mode 100644 index 0000000..97f8798 --- /dev/null +++ b/PhotoBrowser/CustomView/HighResolutionImageView.swift @@ -0,0 +1,69 @@ +// +// HighResolutionImageView.swift +// PhotoBrowser +// +// Created by WangWei on 2019/3/22. +// Copyright © 2019 Teambition. All rights reserved. +// + +import UIKit + +private class FastTiledLayer: CATiledLayer { + override class func fadeDuration() -> CFTimeInterval { + return 0.0 + } +} + +// swiftlint:disable force_cast +final class HighResolutionImageView: UIView { + override class var layerClass: AnyClass { + return FastTiledLayer.self + } + + private var tiledLayer: FastTiledLayer { + return self.layer as! FastTiledLayer + } + + var image: UIImage? { + didSet { + updateTileSize() + } + } + + override init(frame: CGRect) { + super.init(frame: frame) + + contentMode = .scaleAspectFit + layer.contentsGravity = .resizeAspect + + backgroundColor = .white + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private var imageScale: CGFloat = 1.0 + + private var tileSize = CGSize(width: 400, height: 400) + + private func updateTileSize() { + guard let imageSize = image?.size else { return } + + tiledLayer.tileSize = tileSize + + // cal imageScale + imageScale = max(bounds.width / imageSize.width, bounds.height / imageSize.height) + + tiledLayer.levelsOfDetail = 7 + tiledLayer.levelsOfDetailBias = Int(exactly: ceil(log2(1 / imageScale))) ?? 3 + } + + override func draw(_ rect: CGRect) { + guard let cgImage = image?.cgImage else { return } + let scaledRect = rect.applying(CGAffineTransform(scaleX: 1 / imageScale, y: 1 / imageScale)) + let croppedCGImage = cgImage.cropping(to: scaledRect)! + let croppedImage = UIImage(cgImage: croppedCGImage) + croppedImage.draw(in: rect) + } +} diff --git a/PhotoBrowser/CustomView/ImageContainerView.swift b/PhotoBrowser/CustomView/ImageContainerView.swift new file mode 100644 index 0000000..d712af3 --- /dev/null +++ b/PhotoBrowser/CustomView/ImageContainerView.swift @@ -0,0 +1,79 @@ +// +// ImageContainerView.swift +// PhotoBrowser +// +// Created by WangWei on 2019/3/26. +// Copyright © 2019 Teambition. All rights reserved. +// + +import UIKit +import Kingfisher + +final class ImageContainerView: UIView { + lazy var highResImageView: HighResolutionImageView = { + let imageView = HighResolutionImageView() + imageView.layer.masksToBounds = true + return imageView + }() + + lazy var normalImageView: UIImageView = { + let imageView = UIImageView() + imageView.layer.masksToBounds = true + imageView.contentMode = .scaleAspectFit + return imageView + }() + + var image: UIImage? { + didSet { + update(with: image) + } + } + + override init(frame: CGRect) { + super.init(frame: frame) + + setup() + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func setup() { + addSubview(normalImageView) + addSubview(highResImageView) + normalImageView.frame = bounds + highResImageView.frame = bounds + } + + override func layoutSubviews() { + super.layoutSubviews() + normalImageView.frame = bounds + highResImageView.frame = bounds + } + + private func update(with image: UIImage?) { + guard let image = image else { + highResImageView.image = nil + normalImageView.image = nil + return + } + let shouldUseHighResImageView = + image.size.width * image.size.height > 3000 * 3000 + highResImageView.isHidden = !shouldUseHighResImageView + if shouldUseHighResImageView { + highResImageView.frame = bounds + highResImageView.image = image + let size = bounds.size + DispatchQueue.global().async { [weak self] in + let downsampled = image.kf.resize(to: size, for: .aspectFit) + DispatchQueue.main.async { + self?.normalImageView.image = downsampled + } + } + } else { + normalImageView.frame = bounds + normalImageView.image = image + } + } +} diff --git a/PhotoBrowser/Extensions/Kingfisher+CacheSerializer.swift b/PhotoBrowser/Extensions/Kingfisher+CacheSerializer.swift new file mode 100644 index 0000000..b7dbe97 --- /dev/null +++ b/PhotoBrowser/Extensions/Kingfisher+CacheSerializer.swift @@ -0,0 +1,43 @@ +// +// Kingfisher+CacheSerializer.swift +// PhotoBrowser +// +// Created by WangWei on 2019/4/2. +// Copyright © 2019 Teambition. All rights reserved. +// + +import Kingfisher + +struct CustomCacheSerializer: CacheSerializer { + static let `default` = CustomCacheSerializer() + private init() {} + + func data(with image: Image, original: Data?) -> Data? { + // do nothing when image.size is too large + if image.size.width * image.size.height > 3000 * 3000 { + return original + } + + let imageFormat = original?.kf.imageFormat ?? .unknown + + let data: Data? + switch imageFormat { + case .PNG: data = image.kf.pngRepresentation() + case .JPEG: data = image.kf.jpegRepresentation(compressionQuality: 1.0) + case .GIF: data = image.kf.gifRepresentation() + case .unknown: data = original ?? image.kf.normalized.kf.pngRepresentation() + } + + return data + } + + func image(with data: Data, options: KingfisherParsedOptionsInfo) -> Image? { + let imageCreatingOptions = + ImageCreatingOptions(scale: options.scaleFactor, + duration: 0.0, + preloadAll: options.preloadAllAnimationData, + onlyFirstFrame: options.onlyLoadFirstFrame) + return KingfisherWrapper.image(data: data, + options: imageCreatingOptions) + } +} diff --git a/PhotoBrowser/PhotoBrowser.swift b/PhotoBrowser/PhotoBrowser.swift index 52a9009..c283cc8 100644 --- a/PhotoBrowser/PhotoBrowser.swift +++ b/PhotoBrowser/PhotoBrowser.swift @@ -328,7 +328,8 @@ extension PhotoBrowser: UIPageViewControllerDataSource, UIPageViewControllerDele guard let viewController = viewController as? PhotoPreviewController else { return nil } - guard let index = viewController.index, let photos = photos else { + let index = viewController.index + guard let photos = photos else { return nil } if index < 1 { @@ -345,9 +346,7 @@ extension PhotoBrowser: UIPageViewControllerDataSource, UIPageViewControllerDele guard let viewController = viewController as? PhotoPreviewController else { return nil } - guard let index = viewController.index else { - return nil - } + let index = viewController.index guard let photos = photos else { return nil } @@ -366,11 +365,9 @@ extension PhotoBrowser: UIPageViewControllerDataSource, UIPageViewControllerDele guard let currentViewController = pageViewController.viewControllers?.last as? PhotoPreviewController else { return } - if let index = currentViewController.index { - currentIndex = index - updateNavigationBarTitle() - photoBrowserDelegate?.photoBrowser(self, didShowPhotoAtIndex: index) - } + let index = currentViewController.index + currentIndex = index + updateNavigationBarTitle() } } } @@ -413,7 +410,7 @@ extension PhotoBrowser: PhotoPreviewControllerDelegate { view.backgroundColor = UIColor(red: 0, green: 0, blue: 0, alpha: progress) } - func photoPreviewController(_ controller: PhotoPreviewController, doDownDrag isBegin: Bool, needBack: Bool, imageFrame: CGRect, imageView: UIImageView?) { + func photoPreviewController(_ controller: PhotoPreviewController, doDownDrag isBegin: Bool, needBack: Bool, imageFrame: CGRect, imageView: UIView?) { if needBack { // 页面消失 guard let imageView = imageView else { return } UIView.animate(withDuration: 0.25, animations: { @@ -433,7 +430,7 @@ extension PhotoBrowser: PhotoPreviewControllerDelegate { // MARK: - Helpers extension PhotoBrowser { - func currentImageView() -> UIImageView? { + func currentImageView() -> ImageView? { guard let page = viewControllers?.last as? PhotoPreviewController else { return nil } diff --git a/PhotoBrowser/PhotoPreview/Photo.swift b/PhotoBrowser/PhotoPreview/Photo.swift index 8f703c7..91fb5fc 100644 --- a/PhotoBrowser/PhotoPreview/Photo.swift +++ b/PhotoBrowser/PhotoPreview/Photo.swift @@ -11,6 +11,11 @@ import UIKit import Kingfisher import Photos +enum PhotoBrowserError: Error { + case cacheKeyNotExsit + case imageNotExsit +} + public struct Photo { public var image: UIImage? public var thumbnailImage: UIImage? @@ -20,7 +25,7 @@ public struct Photo { public var object: Any? public var asset: PHAsset? public var fileKey: String? - + public init(image: UIImage?, title: String? = nil, thumbnailImage: UIImage? = nil, photoUrl: URL? = nil, thumbnailUrl: URL? = nil, object: Any? = nil, fileKey: String?) { self.image = image self.title = title @@ -35,78 +40,77 @@ public struct Photo { self.asset = asset } - public func localOriginalPhoto(_ completion: @escaping ((UIImage)?) -> Void) { + public func isOriginImageCached() -> Bool { if image != nil { - completion(image) - } else if let originFileKey = fileKey { - let image = KingfisherManager.shared.cache.retrieveImageInMemoryCache(forKey: originFileKey) - if image != nil { - return completion(image) - } else { - KingfisherManager.shared.cache.retrieveImageInDiskCache(forKey: originFileKey) { result in - DispatchQueue.main.async { - switch result { - case .success(let diskImage): - completion(diskImage) - case .failure: - completion(nil) - } - } - } - } - } else if let photoUrl = photoUrl { - let image = KingfisherManager.shared.cache.retrieveImageInMemoryCache(forKey: photoUrl.absoluteString) - if image != nil { - return completion(image) - } else { - KingfisherManager.shared.cache.retrieveImageInDiskCache(forKey: photoUrl.absoluteString) { result in - DispatchQueue.main.async { - switch result { - case .success(let diskImage): - completion(diskImage) - case .failure: - completion(nil) - } - } - } - } + return true } + + guard let cacheKey = fileKey ?? photoUrl?.absoluteString else { + return false + } + + return KingfisherManager.shared.cache.isCached(forKey: cacheKey) } - public func localThumbnailPhoto(_ completion: @escaping ((UIImage)?) -> Void) { + public func isThumbnailCached() -> Bool { + if thumbnailImage != nil { + return true + } + + guard let cacheKey = thumbnailUrl?.absoluteString else { + return false + } + + return KingfisherManager.shared.cache.isCached(forKey: cacheKey) + } + + public func localOriginalPhoto(_ completion: @escaping ((UIImage)?) -> Void) { + if let image = image { + completion(image) + return + } + guard let cacheKey = fileKey ?? photoUrl?.absoluteString else { + completion(nil) + return + } + // retrieve image from cache + let options: KingfisherOptionsInfo = [.preloadAllAnimationData] + KingfisherManager.shared.cache + .retrieveImage(forKey: cacheKey, + options: options, + callbackQueue: .mainAsync) { (result) in + completion(try? result.get().image) + } + } + + public func localThumbnailPhoto(_ completion: @escaping (UIImage?) -> Void) { if thumbnailImage != nil { completion(thumbnailImage) - } else if let thumbnailUrl = thumbnailUrl { - let image = KingfisherManager.shared.cache.retrieveImageInMemoryCache(forKey: thumbnailUrl.absoluteString) - if image != nil { - return completion(image) - } else { - KingfisherManager.shared.cache.retrieveImageInDiskCache(forKey: thumbnailUrl.absoluteString) { result in - DispatchQueue.main.async { - switch result { - case .success(let diskImage): - completion(diskImage) - case .failure: - completion(nil) - } - } - } - } + return + } + guard let cacheKey = thumbnailUrl?.absoluteString else { + completion(nil) + return + } + + let options: KingfisherOptionsInfo = [.preloadAllAnimationData] + KingfisherManager.shared.cache + .retrieveImage(forKey: cacheKey, + options: options, + callbackQueue: .mainAsync) { (result) in + completion(try? result.get().image) } - completion(nil) } - - public func imageToSave(_ completion: @escaping ((UIImage)?) -> Void) { + + public func imageToSave(_ completion: @escaping (UIImage?) -> Void) { localOriginalPhoto { (image) in if image != nil { completion(image) + return } - } - localThumbnailPhoto { (image) in - if image != nil { + self.localThumbnailPhoto { (image) in completion(image) } } - completion(nil) } } diff --git a/PhotoBrowser/PhotoPreview/PhotoPreviewController.swift b/PhotoBrowser/PhotoPreview/PhotoPreviewController.swift index 1f141e8..f97b91a 100644 --- a/PhotoBrowser/PhotoPreview/PhotoPreviewController.swift +++ b/PhotoBrowser/PhotoPreview/PhotoPreviewController.swift @@ -19,18 +19,44 @@ protocol PhotoPreviewControllerDelegate: class { func photoPreviewController(_ controller: PhotoPreviewController, didTapSkitch skitch: Skitch, versionID: String) func photoPreviewController(_ controller: PhotoPreviewController, didShowPhotoAtIndex index: Int) func photoPreviewController(_ controller: PhotoPreviewController, doDraging dragProgress: CGFloat) - func photoPreviewController(_ controller: PhotoPreviewController, doDownDrag isBegin: Bool, needBack: Bool, imageFrame: CGRect, imageView: UIImageView?) + func photoPreviewController(_ controller: PhotoPreviewController, doDownDrag isBegin: Bool, needBack: Bool, imageFrame: CGRect, imageView: UIView?) } +typealias ImageView = ImageContainerView + // MARK: - PhotoPreviewController class PhotoPreviewController: UIViewController { - var index: NSInteger? - var photo: Photo? - lazy var imageView: UIImageView = self.makeImageView() - + var index: NSInteger + var photo: Photo + + lazy var imageView: ImageView = { + let imageView = ImageView() + imageView.layer.masksToBounds = true + imageView.contentMode = .scaleAspectFit + return imageView + }() + private var waitingView: WaitingView? - private lazy var scrollView: UIScrollView = self.makeScrollView() - + private lazy var scrollView: UIScrollView = { + let scrollView = UIScrollView(frame: self.view.frame) + scrollView.delegate = self + scrollView.backgroundColor = .clear + scrollView.clipsToBounds = true + scrollView.showsVerticalScrollIndicator = false + scrollView.showsHorizontalScrollIndicator = false + scrollView.alwaysBounceHorizontal = true + scrollView.alwaysBounceVertical = true + scrollView.minimumZoomScale = 1.0 + scrollView.maximumZoomScale = .greatestFiniteMagnitude + scrollView.zoomScale = 1.0 + scrollView.contentOffset = .zero + + if #available(iOS 11.0, *) { + scrollView.contentInsetAdjustmentBehavior = .never + } + return scrollView + }() + // Support Skitch View private var skitches: [Skitch] = [] private var versionID: String = "" @@ -45,7 +71,7 @@ class PhotoPreviewController: UIViewController { private var minPanY: CGFloat { return (miniMap?.isHidden ?? true) ? -10 : -CGFloat.greatestFiniteMagnitude } - private var moveImage: UIImageView? // 拖拽图片 + private var moveImage: UIView? // 拖拽图片 private var isPanning: Bool = false // 正在拖拽 private var isZooming: Bool = false // 正在缩放 private var panningProgress: CGFloat = 0 // 拖拽进度 @@ -71,35 +97,37 @@ class PhotoPreviewController: UIViewController { updateMiniMapLayout() } } - + // Support MiniMap private var miniMap: MiniMap? private var miniMapTopConstraint: NSLayoutConstraint? - public var miniMapSize: CGSize = CGSize(width: 100, height: 100) - - init(photo: Photo, index: NSInteger, skitches: [[String: Any]]? = nil, isSkitchButtonHidden: Bool = true) { - super.init(nibName: nil, bundle: nil) + public var miniMapSize = CGSize(width: 100, height: 100) + + required init(photo: Photo, index: NSInteger, skitches: [[String: Any]]? = nil, isSkitchButtonHidden: Bool = true) { self.index = index self.photo = photo + + super.init(nibName: nil, bundle: nil) + self.isSkitchButtonHidden = isSkitchButtonHidden self.initSkitches(skitches) - self.loadCloudKitPhoto(at: index) + self.loadCloudKitPhoto() } required init?(coder aDecoder: NSCoder) { - super.init(coder: aDecoder) + fatalError("init(coder:) has not been implemented") } override func viewDidLoad() { commonInit() } - + override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { super.viewWillTransition(to: size, with: coordinator) scrollView.frame = CGRect(x: 0, y: 0, width: size.width, height: size.height) - loadNetworkPhoto() + loadImage(with: photo) } - + private func commonInit() { extendedLayoutIncludesOpaqueBars = true automaticallyAdjustsScrollViewInsets = false @@ -110,26 +138,26 @@ class PhotoPreviewController: UIViewController { scrollView.addSubview(imageView) imageView.isUserInteractionEnabled = true - + let doubleTap = UITapGestureRecognizer.init(target: self, action: #selector(handleDoubleTap(_:))) doubleTap.numberOfTapsRequired = 2 imageView.addGestureRecognizer(doubleTap) - + let singleTap = UITapGestureRecognizer.init(target: self, action: #selector(handleSingleTap(_:))) singleTap.numberOfTapsRequired = 1 imageView.addGestureRecognizer(singleTap) - + let longPress = UILongPressGestureRecognizer.init(target: self, action: #selector(handleLongPress(_:))) imageView.addGestureRecognizer(longPress) let backgroudSingleTap = UITapGestureRecognizer.init(target: self, action: #selector(handleBackgroundSingleTap(_:))) backgroudSingleTap.numberOfTapsRequired = 1 view.addGestureRecognizer(backgroudSingleTap) - + singleTap.require(toFail: doubleTap) - + miniMap = makeMiniMap() - loadNetworkPhoto() + loadImage(with: photo) } } @@ -141,7 +169,7 @@ extension PhotoPreviewController { let actualCenter = CGPoint(x: scrollView.contentSize.width * 0.5 + offsetX, y: scrollView.contentSize.height * 0.5 + offsetY) return actualCenter } - + private func setImageViewFrame(_ image: UIImage) { DispatchQueue.main.async { [weak self] in guard let strongSelf = self else { @@ -170,9 +198,40 @@ extension PhotoPreviewController { strongSelf.scrollView.contentSize = strongSelf.imageView.frame.size } } - - private func loadCloudKitPhoto(at index: NSInteger) { - guard let asset = self.photo?.asset, self.photo?.image == nil else { + + private func updateMiniMap(with image: UIImage) { + let pointSize = miniMapSize + DispatchQueue.global().async { [weak self] in + // downsample image to minimap size to reduce memory cost + let downsampled = image.kf.resize(to: pointSize, for: .aspectFit) + DispatchQueue.main.async { + self?.miniMap?.image = downsampled + } + } + } + + private func updateImageViewFrameWithImageSize(_ imageSize: CGSize) { + imageOriginWidth = imageSize.width + imageView.frame = scrollView.bounds + + var imageViewSize = imageSize + let imageScale = min(scrollView.bounds.width / imageSize.width, + scrollView.bounds.height / imageSize.height) + if imageScale > 1 { + imageViewSize = imageSize + } else { + imageViewSize = CGSize(width: imageSize.width * imageScale, + height: imageSize.height * imageScale) + } + + imageView.size = imageViewSize + imageView.center = scrollView.center + scrollView.contentSize = imageView.frame.size + } + + private func loadCloudKitPhoto() { + guard let asset = self.photo.asset, + self.photo.image == nil else { return } @@ -181,12 +240,12 @@ extension PhotoPreviewController { waitingView.removeFromSuperview() } waitingView = WaitingView(frame: CGRect(x: 0, y: 0, width: 70, height: 70)) - + if let newWaitingView = waitingView { newWaitingView.center = view.center view.addSubview(newWaitingView) } - + let options = PHImageRequestOptions() options.deliveryMode = .highQualityFormat options.isSynchronous = false @@ -196,17 +255,18 @@ extension PhotoPreviewController { self?.waitingView?.progress = CGFloat(progress) } } - + // Request data PHImageManager.default().requestImageData(for: asset, options: options) { [weak self] data, _, _, _ in guard let strongSelf = self else { return } DispatchQueue.main.async { if let imageData = data, let image = UIImage(data: imageData) { - strongSelf.photo?.image = image - strongSelf.setImageViewFrame(image) + strongSelf.photo.image = image + strongSelf.updateImageViewFrameWithImageSize(image.size) strongSelf.imageView.image = image strongSelf.addSkitches() - strongSelf.delegate?.photoPreviewController(strongSelf, didShowPhotoAtIndex: index) + strongSelf.delegate?.photoPreviewController(strongSelf, + didShowPhotoAtIndex: strongSelf.index) } if let waitingView = strongSelf.waitingView { waitingView.removeFromSuperview() @@ -214,97 +274,96 @@ extension PhotoPreviewController { } } } - - private func loadNetworkPhoto() { - guard let photo = photo else { - return + + private func loadImage(with photo: Photo) { + let setImage: (UIImage) -> Void = { [weak self] (image) in + guard let strongSelf = self else { return } + strongSelf.updateImageViewFrameWithImageSize(image.size) + strongSelf.imageView.image = image + strongSelf.updateMiniMap(with: image) + strongSelf.addSkitches() + strongSelf.delegate?.photoPreviewController(strongSelf, + didShowPhotoAtIndex: strongSelf.index) } - photo.localOriginalPhoto { [weak self] (image) in - guard let strongSelf = self else { return } - if let image = image { - strongSelf.loadLocalOriginalPhoto(image) - } else { - strongSelf.downloadPhoto(photo) + let downloadCompleted: (Result) -> Void = { [weak self] (result) in + DispatchQueue.main.async { + guard let strongSelf = self else { return } + strongSelf.dismissProgressView() + if let image = try? result.get().image { + setImage(image) + } } } - } - - private func loadLocalOriginalPhoto(_ image: UIImage) { - setImageViewFrame(image) - imageView.image = image - addSkitches() - if let index = index { - delegate?.photoPreviewController(self, didShowPhotoAtIndex: index) - } - } - - private func downloadPhoto(_ photo: Photo) { - photo.localThumbnailPhoto { [weak self] (thumbnail) in - guard let strongSelf = self else { return } - if let thumbnail = thumbnail { - strongSelf.setImageViewFrame(thumbnail) - strongSelf.imageView.image = thumbnail - } - } - if let waitingView = waitingView { - waitingView.removeFromSuperview() + let progressUpdated: DownloadProgressBlock + = { [weak self] (received, total) in + DispatchQueue.main.async { + guard let strongSelf = self else { return } + let progress = CGFloat(received) / CGFloat(total) + strongSelf.waitingView?.progress = progress + } } - guard let photoUrl = photo.photoUrl, let photoFileKey = photo.fileKey else { + if photo.isOriginImageCached() { + photo.localOriginalPhoto { (image) in + if let originImage = image { + setImage(originImage) + } + } return } - waitingView = WaitingView.init(frame: CGRect(x: 0, y: 0, width: 70, height: 70)) - if let newWaitingView = waitingView { - newWaitingView.center = view.center - view.addSubview(newWaitingView) + if photo.isThumbnailCached() { + photo.localThumbnailPhoto { (thumbnail) in + if let thumbnail = thumbnail { + // show thumbnail first when exists + setImage(thumbnail) + } + } } - let resource = ImageResource(downloadURL: photoUrl, cacheKey: photoFileKey) + // load image from url + guard let url = photo.photoUrl else { return } + // specify cacheKey to fileKey if exists + let imageResource = ImageResource(downloadURL: url, cacheKey: photo.fileKey) + + showProgressView() + let options: KingfisherOptionsInfo = [.preloadAllAnimationData, + .cacheSerializer(CustomCacheSerializer.default)] + KingfisherManager.shared.retrieveImage(with: imageResource, + options: options, + progressBlock: progressUpdated, + completionHandler: downloadCompleted) + } - photo.localThumbnailPhoto { [weak self] image in - guard let strongSelf = self else { - return - } - strongSelf.imageView.kf.setImage(with: resource, placeholder: image ?? photo.image, options: nil, progressBlock: { [weak self] (receivedSize, totalSize) -> Void in - guard let strongSelf = self else { return } - let progress = CGFloat(receivedSize) / CGFloat(totalSize) - if let waitingView = strongSelf.waitingView { - waitingView.progress = progress - } - }, completionHandler: { [weak self] (result) -> Void in - guard let strongSelf = self else { return } - switch result { - case .success(let retrieveImageResult): - if let waitingView = strongSelf.waitingView { - waitingView.removeFromSuperview() - } - strongSelf.setImageViewFrame(retrieveImageResult.image) - strongSelf.addSkitches() - if let index = strongSelf.index { - strongSelf.delegate?.photoPreviewController(strongSelf, didShowPhotoAtIndex: index) - } - case .failure: - print("fetch image error") - } - }) + private func showProgressView() { + if let old = waitingView { + old.removeFromSuperview() } + + let progressView = WaitingView(frame: CGRect(x: 0, y: 0, width: 70, height: 70)) + progressView.center = view.center + view.addSubview(progressView) + waitingView = progressView } - + + private func dismissProgressView() { + waitingView?.removeFromSuperview() + } + private func updateConstraint() { updateSkitchViewConstraint() view.layoutIfNeeded() } - + private func zoomScaleForDoubleTap() -> CGFloat { guard imageView.image != nil else { return scrollView.minimumZoomScale } return 2 * scrollView.minimumZoomScale } - + private func updateMiniMapLayout() { guard miniMap != nil else { return @@ -318,7 +377,7 @@ extension PhotoPreviewController { self.view.layoutIfNeeded() } } - + private func initSkitches(_ skitches: [[String: Any]]? = nil) { if let skitches = skitches { self.skitches = skitches.compactMap({ (skitchJSON) -> Skitch? in @@ -330,33 +389,6 @@ extension PhotoPreviewController { // MARK: - factory methods extension PhotoPreviewController { - private func makeScrollView() -> UIScrollView { - let scrollView = UIScrollView(frame: self.view.frame) - scrollView.delegate = self - scrollView.backgroundColor = .clear - scrollView.clipsToBounds = true - scrollView.showsVerticalScrollIndicator = false - scrollView.showsHorizontalScrollIndicator = false - scrollView.alwaysBounceHorizontal = true - scrollView.alwaysBounceVertical = true - scrollView.minimumZoomScale = 1.0 - scrollView.maximumZoomScale = CGFloat.greatestFiniteMagnitude - scrollView.zoomScale = 1.0 - scrollView.contentOffset = .zero - - if #available(iOS 11.0, *) { - scrollView.contentInsetAdjustmentBehavior = .never - } - return scrollView - } - - private func makeImageView() -> UIImageView { - let imageView = UIImageView() - imageView.layer.masksToBounds = true - imageView.contentMode = .scaleAspectFit - return imageView - } - private func makeMiniMap() -> MiniMap { let miniMap = MiniMap(size: miniMapSize) miniMap.isHidden = true @@ -397,7 +429,7 @@ extension PhotoPreviewController { } updateConstraint() } - + @objc private func handleSingleTap(_ sender: UITapGestureRecognizer) { guard let delegate = delegate else { return @@ -405,9 +437,9 @@ extension PhotoPreviewController { isFullScreenMode = !isFullScreenMode delegate.isFullScreenMode = !delegate.isFullScreenMode } - + @objc private func handleLongPress(_ sender: UILongPressGestureRecognizer) { - guard let delegate = delegate, let photo = photo else { + guard let delegate = delegate else { return } if sender.state == .began { @@ -418,7 +450,7 @@ extension PhotoPreviewController { @objc private func handleBackgroundSingleTap(_ sender: UITapGestureRecognizer) { delegate?.didTapBackground(self) } - + @objc private func hideMiniMap() { miniMap?.isHidden = true } @@ -435,15 +467,15 @@ extension PhotoPreviewController: UIScrollViewDelegate { isZooming = false updateConstraint() } - + func scrollViewWillBeginZooming(_ scrollView: UIScrollView, with view: UIView?) { isZooming = true } - + func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer) { endPan() } - + func scrollViewDidScroll(_ scrollView: UIScrollView) { /// sometime, iOS called once `scrollViewDidScroll` when after `scrollViewDidEndZooming` if afterZooming { @@ -455,16 +487,16 @@ extension PhotoPreviewController: UIScrollViewDelegate { if scrollView.panGestureRecognizer.state == .began { scrollOldOffset = scrollView.contentOffset } - + scrollNewOffset = scrollView.contentOffset if !isZooming { if isPanning { - doPan(scrollView.panGestureRecognizer) + onPan(scrollView.panGestureRecognizer) } else { checkPanGesture(scrollView) } } - + miniMap?.isHidden = scrollView.contentSize.width <= view.frame.width || moveImage != nil miniMap?.ratios = Ratios( @@ -474,7 +506,7 @@ extension PhotoPreviewController: UIScrollViewDelegate { height: view.frame.height / scrollView.contentSize.height ) } - + func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) { if scrollView.panGestureRecognizer.state == .ended { perform(#selector(hideMiniMap), with: self, afterDelay: 3, inModes: [.default]) @@ -495,13 +527,13 @@ extension PhotoPreviewController { guard scrollNewOffset.y < scrollOldOffset.y else { return } - + if scrollView.contentOffset.y < minPanY { let x = abs(scrollNewOffset.x - scrollOldOffset.x) let y = abs(scrollNewOffset.y - scrollOldOffset.y) let minTan: CGFloat = 0.577 // tan 30 if x / y < minTan { - doPan(scrollView.panGestureRecognizer) + onPan(scrollView.panGestureRecognizer) } } } @@ -514,15 +546,15 @@ extension PhotoPreviewController { //计算图片centerY需要考虑到图片此时的高 let imageBeginY = (imageHeightBeforeDrag < UIScreen.main.bounds.size.height) ? (UIScreen.main.bounds.size.height - imageHeightBeforeDrag) * 0.5 : 0.0 imageYBeforeDrag = imageBeginY - + //centerX需要考虑到offset scrollOffsetX = self.scrollView.contentOffset.x let imageX = -scrollOffsetX imageCenterXBeforeDrag = imageX + imageWidthBeforeDrag * 0.5 dragCoefficient = 1.0 + imageHeightBeforeDrag / 2000.0 } - - private func doPan(_ pan: UIPanGestureRecognizer) { + + private func onPan(_ pan: UIPanGestureRecognizer) { if pan.state == .ended || pan.state == .possible { // 手势已结束 panBeginX = 0 panBeginY = 0 @@ -532,7 +564,7 @@ extension PhotoPreviewController { } return } - + if pan.numberOfTouches != 1 || isZooming { // 两个手指在拖,此时在缩放 moveImage = nil isPanning = false @@ -540,7 +572,7 @@ extension PhotoPreviewController { panBeginY = 0 return } - + if panBeginX == 0.0 && panBeginY == 0.0 { // 新的一次下拉开始了 panBeginX = pan.location(in: self.view).x panBeginY = pan.location(in: self.view).y @@ -550,23 +582,30 @@ extension PhotoPreviewController { saveFrameBeginPan() delegate?.photoPreviewController(self, doDownDrag: true, needBack: false, imageFrame: CGRect.zero, imageView: nil) } - + if moveImage == nil { // 添加moveImage - moveImage = UIImageView() + let imageSizeBeforeDrag = CGSize(width: imageWidthBeforeDrag, + height: imageHeightBeforeDrag) + let shotImageView = imageView.snapshotView(afterScreenUpdates: false) + moveImage = shotImageView + moveImage?.frame = CGRect(origin: .zero, size: imageSizeBeforeDrag) +// moveImage = UIImageView(frame: CGRect(origin: .zero, size: imageSizeBeforeDrag)) view.addSubview(moveImage!) moveImage?.contentMode = .scaleAspectFill moveImage?.backgroundColor = .white moveImage?.layer.masksToBounds = true - moveImage?.image = imageView.image +// let adjustedSize = CGSize(width: ceil(imageSizeBeforeDrag.width), height: ceil(imageSizeBeforeDrag.height)) +// moveImage?.image = imageView.image?.kf.resize(to: adjustedSize, +// for: .aspectFill) moveImage?.width = imageWidthBeforeDrag moveImage?.height = imageHeightBeforeDrag moveImage?.center.x = imageCenterXBeforeDrag moveImage?.originY = imageYBeforeDrag } - + let panCurrentX: CGFloat = pan.location(in: view).x let panCurrentY: CGFloat = pan.location(in: view).y - + // 判断是否向下拖拽 isDirectionDown = panCurrentY > panLastY panLastY = panCurrentY @@ -586,7 +625,7 @@ extension PhotoPreviewController { moveImage?.center.x = (panCurrentX - panBeginX) + imageCenterXBeforeDrag moveImage?.originY = (panCurrentY - panBeginY) * dragCoefficient + imageYBeforeDrag } - + private func endPan() { if !isDirectionDown { // 不退回页面 guard moveImage != nil else { diff --git a/PhotoBrowserDemo.xcodeproj/project.pbxproj b/PhotoBrowserDemo.xcodeproj/project.pbxproj index 36f4688..8e47f7b 100644 --- a/PhotoBrowserDemo.xcodeproj/project.pbxproj +++ b/PhotoBrowserDemo.xcodeproj/project.pbxproj @@ -333,7 +333,7 @@ isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = 9EF7268BM7; FRAMEWORK_SEARCH_PATHS = ( "$(PROJECT_DIR)/Carthage/Build/iOS", "$(PROJECT_DIR)", @@ -350,7 +350,7 @@ isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = 9EF7268BM7; FRAMEWORK_SEARCH_PATHS = ( "$(PROJECT_DIR)/Carthage/Build/iOS", "$(PROJECT_DIR)", diff --git a/PhotoBrowserDemo/AppDelegate.swift b/PhotoBrowserDemo/AppDelegate.swift index b0a84f8..6057e84 100644 --- a/PhotoBrowserDemo/AppDelegate.swift +++ b/PhotoBrowserDemo/AppDelegate.swift @@ -2,7 +2,7 @@ // AppDelegate.swift // PhotoBrowserDemo // -// Created by 王卫 on 16/2/16. +// Created by WangWei on 16/2/16. // Copyright © 2016年 Teambition. All rights reserved. // diff --git a/PhotoBrowserDemo/Assets.xcassets/original2.imageset/pic2.png b/PhotoBrowserDemo/Assets.xcassets/original2.imageset/pic2.png deleted file mode 100644 index 6afde67..0000000 Binary files a/PhotoBrowserDemo/Assets.xcassets/original2.imageset/pic2.png and /dev/null differ diff --git a/PhotoBrowserDemo/Assets.xcassets/original3.imageset/Contents.json b/PhotoBrowserDemo/Assets.xcassets/original3.imageset/Contents.json deleted file mode 100644 index abbac69..0000000 --- a/PhotoBrowserDemo/Assets.xcassets/original3.imageset/Contents.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "filename" : "image.jpeg", - "scale" : "1x" - }, - { - "idiom" : "universal", - "scale" : "2x" - }, - { - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/PhotoBrowserDemo/Assets.xcassets/original3.imageset/image.jpeg b/PhotoBrowserDemo/Assets.xcassets/original3.imageset/image.jpeg deleted file mode 100644 index ad37593..0000000 Binary files a/PhotoBrowserDemo/Assets.xcassets/original3.imageset/image.jpeg and /dev/null differ diff --git a/PhotoBrowserDemo/Assets.xcassets/thumbnail1.imageset/thumbnail1.jpeg b/PhotoBrowserDemo/Assets.xcassets/thumbnail1.imageset/thumbnail1.jpeg deleted file mode 100644 index 661302c..0000000 Binary files a/PhotoBrowserDemo/Assets.xcassets/thumbnail1.imageset/thumbnail1.jpeg and /dev/null differ diff --git a/PhotoBrowserDemo/Assets.xcassets/thumbnail2.imageset/thumbnail2.png b/PhotoBrowserDemo/Assets.xcassets/thumbnail2.imageset/thumbnail2.png deleted file mode 100644 index 60c077d..0000000 Binary files a/PhotoBrowserDemo/Assets.xcassets/thumbnail2.imageset/thumbnail2.png and /dev/null differ diff --git a/PhotoBrowserDemo/Assets.xcassets/thumbnail3.imageset/Contents.json b/PhotoBrowserDemo/Assets.xcassets/thumbnail3.imageset/Contents.json deleted file mode 100644 index abbac69..0000000 --- a/PhotoBrowserDemo/Assets.xcassets/thumbnail3.imageset/Contents.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "filename" : "image.jpeg", - "scale" : "1x" - }, - { - "idiom" : "universal", - "scale" : "2x" - }, - { - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/PhotoBrowserDemo/Assets.xcassets/thumbnail3.imageset/image.jpeg b/PhotoBrowserDemo/Assets.xcassets/thumbnail3.imageset/image.jpeg deleted file mode 100644 index b5d54ab..0000000 Binary files a/PhotoBrowserDemo/Assets.xcassets/thumbnail3.imageset/image.jpeg and /dev/null differ diff --git a/PhotoBrowserDemo/Assets.xcassets/thumbnail1.imageset/Contents.json b/PhotoBrowserDemo/Assets.xcassets/thumbnail_one.imageset/Contents.json similarity index 87% rename from PhotoBrowserDemo/Assets.xcassets/thumbnail1.imageset/Contents.json rename to PhotoBrowserDemo/Assets.xcassets/thumbnail_one.imageset/Contents.json index effca15..f50780a 100644 --- a/PhotoBrowserDemo/Assets.xcassets/thumbnail1.imageset/Contents.json +++ b/PhotoBrowserDemo/Assets.xcassets/thumbnail_one.imageset/Contents.json @@ -2,11 +2,11 @@ "images" : [ { "idiom" : "universal", - "filename" : "thumbnail1.jpeg", "scale" : "1x" }, { "idiom" : "universal", + "filename" : "thumbnail_one.jpg", "scale" : "2x" }, { diff --git a/PhotoBrowserDemo/Assets.xcassets/thumbnail_one.imageset/thumbnail_one.jpg b/PhotoBrowserDemo/Assets.xcassets/thumbnail_one.imageset/thumbnail_one.jpg new file mode 100644 index 0000000..365f695 Binary files /dev/null and b/PhotoBrowserDemo/Assets.xcassets/thumbnail_one.imageset/thumbnail_one.jpg differ diff --git a/PhotoBrowserDemo/Assets.xcassets/thumbnail2.imageset/Contents.json b/PhotoBrowserDemo/Assets.xcassets/thumbnail_three.imageset/Contents.json similarity index 86% rename from PhotoBrowserDemo/Assets.xcassets/thumbnail2.imageset/Contents.json rename to PhotoBrowserDemo/Assets.xcassets/thumbnail_three.imageset/Contents.json index 455e7be..aabf273 100644 --- a/PhotoBrowserDemo/Assets.xcassets/thumbnail2.imageset/Contents.json +++ b/PhotoBrowserDemo/Assets.xcassets/thumbnail_three.imageset/Contents.json @@ -2,11 +2,11 @@ "images" : [ { "idiom" : "universal", - "filename" : "thumbnail2.png", "scale" : "1x" }, { "idiom" : "universal", + "filename" : "thumbnail_three.jpg", "scale" : "2x" }, { diff --git a/PhotoBrowserDemo/Assets.xcassets/thumbnail_three.imageset/thumbnail_three.jpg b/PhotoBrowserDemo/Assets.xcassets/thumbnail_three.imageset/thumbnail_three.jpg new file mode 100644 index 0000000..898a754 Binary files /dev/null and b/PhotoBrowserDemo/Assets.xcassets/thumbnail_three.imageset/thumbnail_three.jpg differ diff --git a/PhotoBrowserDemo/Assets.xcassets/original2.imageset/Contents.json b/PhotoBrowserDemo/Assets.xcassets/thumbnail_two.imageset/Contents.json similarity index 87% rename from PhotoBrowserDemo/Assets.xcassets/original2.imageset/Contents.json rename to PhotoBrowserDemo/Assets.xcassets/thumbnail_two.imageset/Contents.json index c1df181..547874f 100644 --- a/PhotoBrowserDemo/Assets.xcassets/original2.imageset/Contents.json +++ b/PhotoBrowserDemo/Assets.xcassets/thumbnail_two.imageset/Contents.json @@ -2,11 +2,11 @@ "images" : [ { "idiom" : "universal", - "filename" : "pic2.png", "scale" : "1x" }, { "idiom" : "universal", + "filename" : "thumbnail_two.jpg", "scale" : "2x" }, { diff --git a/PhotoBrowserDemo/Assets.xcassets/thumbnail_two.imageset/thumbnail_two.jpg b/PhotoBrowserDemo/Assets.xcassets/thumbnail_two.imageset/thumbnail_two.jpg new file mode 100644 index 0000000..b68977b Binary files /dev/null and b/PhotoBrowserDemo/Assets.xcassets/thumbnail_two.imageset/thumbnail_two.jpg differ diff --git a/PhotoBrowserDemo/Base.lproj/Main.storyboard b/PhotoBrowserDemo/Base.lproj/Main.storyboard index 7d21e75..d5e08d1 100644 --- a/PhotoBrowserDemo/Base.lproj/Main.storyboard +++ b/PhotoBrowserDemo/Base.lproj/Main.storyboard @@ -1,12 +1,11 @@ - + - - + @@ -22,7 +21,7 @@ - + @@ -67,7 +66,7 @@ - + diff --git a/PhotoBrowserDemo/ViewController.swift b/PhotoBrowserDemo/ViewController.swift index dd85dff..78c5a5d 100644 --- a/PhotoBrowserDemo/ViewController.swift +++ b/PhotoBrowserDemo/ViewController.swift @@ -2,18 +2,37 @@ // ViewController.swift // PhotoBrowserDemo // -// Created by 王卫 on 16/2/16. +// Created by WangWei on 16/2/16. // Copyright © 2016年 Teambition. All rights reserved. // import UIKit import PhotoBrowser +enum DemoImageConstants { + static let imageOne = "https://photojournal.jpl.nasa.gov/jpeg/PIA23010.jpg" + static let imageTwo = "https://photojournal.jpl.nasa.gov/jpeg/PIA22094.jpg" + static let imageThree = "https://photojournal.jpl.nasa.gov/jpeg/PIA23004.jpg" + static let gif = "https://www.sample-videos.com/gif/2.gif" + + static var thumbnailOne: UIImage? { + return UIImage(named: "thumbnail_one") + } + + static var thumbnailTwo: UIImage? { + return UIImage(named: "thumbnail_two") + } + + static var thumbnailThree: UIImage? { + return UIImage(named: "thumbnail_three") + } +} + class ViewController: UIViewController { var photoBrowser: PhotoBrowser? - + @IBOutlet weak var imageView: UIImageView! - + override func viewDidLoad() { super.viewDidLoad() let gesture = UITapGestureRecognizer(target: self, action: #selector(displayPhotoBrowser)) @@ -36,48 +55,50 @@ class ViewController: UIViewController { extension ViewController { @objc func displayPhotoBrowser() { - let thumbnail1 = UIImage.init(named: "thumbnail1") - let photoUrl1 = URL.init(string: "https://pic1.zhimg.com/0f70807392a9f62528b00ec434f5519c_b.png") - let thumbnail2 = UIImage.init(named: "thumbnail2") - let photoUrl2 = URL.init(string: "https://pic1.zhimg.com/0f70807392a9f62528b00ec434f5519c_b.png") - let thumbnail3 = UIImage.init(named: "thumbnail3") - let photoUrl3 = URL.init(string: "https://pic2.zhimg.com/a5455838750e168d97480d9247537d31_r.jpeg") - - let photo = Photo.init(image: nil, title: "Image1fjdkkfadjfkajdkfalkdsfjklasfklaskdfkadsjfklajklsdjfkajsdkfaksdjfkajsdkfjlaksdfjkakdfklak", thumbnailImage: thumbnail1, photoUrl: photoUrl1, fileKey: "abc100") - let photo2 = Photo.init(image: nil, title: "Image2", thumbnailImage: thumbnail2, photoUrl: photoUrl2, fileKey: "abc101") - let photo3 = Photo.init(image: nil, title: "Image3", thumbnailImage: thumbnail3, photoUrl: photoUrl3, fileKey: "abc102") - - let item1 = PBActionBarItem(title: "ONE", style: .plain) { (photoBrowser, _) in - let photos = [photo, photo2] + let photo1 = Photo(image: nil, title: "2412*1713", + thumbnailImage: DemoImageConstants.thumbnailOne, + photoUrl: URL(string: DemoImageConstants.imageOne), + fileKey: nil) + let photo2 = Photo(image: nil, + title: "6000*3500", + thumbnailImage: DemoImageConstants.thumbnailTwo, + photoUrl: URL(string: DemoImageConstants.imageTwo), + fileKey: nil) + let photo3 = Photo(image: nil, + title: "6000*3375", + thumbnailImage: DemoImageConstants.thumbnailThree, + photoUrl: URL(string: DemoImageConstants.imageThree), + fileKey: nil) + let photo4 = Photo(image: nil, + title: "gif", + thumbnailImage: nil, + photoUrl: URL(string: DemoImageConstants.gif), + fileKey: nil) + + let item1 = PBActionBarItem(title: "😄", style: .plain) { (photoBrowser, _) in + let photos = [photo1, photo2] photoBrowser.photos = photos } - let item2 = PBActionBarItem(title: "TWO", style: .plain) { (photoBrowser, _) in -// photoBrowser.enableShare = !photoBrowser.enableShare - print("item2") + let item2 = PBActionBarItem(title: "👌", style: .plain) { (photoBrowser, _) in let vc = UIStoryboard.init(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "DetailViewController") self.photoBrowser?.navigationController?.pushViewController(vc, animated: true) } - let item3 = PBActionBarItem(title: "THREE", style: .plain) { (_, _) in + let item3 = PBActionBarItem(title: "✈️", style: .plain) { (_, _) in print("item3") } - let item4 = PBActionBarItem(title: "FOUR", style: .plain) { (_, _) in + let item4 = PBActionBarItem(title: "🚢", style: .plain) { (_, _) in print("item3") } photoBrowser = PhotoBrowser() if let browser = photoBrowser { -// browser.isFromPhotoPicker = true - browser.selectedIndex = [0, 1] - browser.photos = [photo, photo2, photo3] + browser.photos = [photo1, photo2, photo3, photo4] browser.actionItems = [item1, item2, item3, item4] browser.photoBrowserDelegate = self browser.currentIndex = 0 -// browser.isFromPhotoPicker = true browser.isShowMoreButton = false -// browser.isPreviewMode = false -// present(browser, animated: true, completion: nil) presentPhotoBrowser(browser, fromView: imageView) } } @@ -85,39 +106,32 @@ extension ViewController { extension ViewController: PhotoBrowserDelegate { func photoBrowser(_ browser: PhotoBrowser, didTapSkitch skitch: Skitch, versionID: String) { -// print("didTapSkichAtIndex tapped: \(skitch.number)") } func photoBrowser(_ browser: PhotoBrowser, willShowPhotoAtIndex index: Int) { -// print("will show photo at index: \(index)") } - + func photoBrowser(_ browser: PhotoBrowser, didHideSkitchButton isHidden: Bool) { -// if isHidden { -// print("hidden") -// } else { -// print("visible") -// } } func photoBrowser(_ browser: PhotoBrowser, didShowPhotoAtIndex index: Int) { -// print("photo browser did show at index: \(index)") -// let points1 = ["x": 100.0, "y": 50.0, "width": 50.0, "height": 50.0] let points21 = ["x": 0, "y": 0, "width": 20.0, "height": 20.0] let points22 = ["x": 230.0, "y": 230.0, "width": 20.0, "height": 20.0] let points23 = ["x": 250.0, "y": 250.0, "width": 50.0, "height": 50.0] let points3 = ["x": 80.0, "y": 80.0, "width": 10.0, "height": 10.0] -// browser.updatePhotoSkitch(at: 0, skitches: [["_id": "120", "num": 1, "type": "point", "coordinate": points1]], versionID: "kkk") - browser.updatePhotoSkitch(at: 1, skitches: [["_id": "121", "num": 2, "type": "point", "coordinate": points21], - ["_id": "122", "num": 3, "type": "point", "coordinate": points22], - ["_id": "123", "num": 4, "type": "point", "coordinate": points23]], versionID: "ggg") - browser.updatePhotoSkitch(at: 2, skitches: [["_id": "124", "num": 3, "type": "point", "coordinate": points3]], versionID: "zzz") + if index == 1 { + browser.updatePhotoSkitch(at: 1, skitches: [["_id": "121", "num": 2, "type": "point", "coordinate": points21], + ["_id": "122", "num": 3, "type": "point", "coordinate": points22], + ["_id": "123", "num": 4, "type": "point", "coordinate": points23]], versionID: "ggg") + } else if index == 2 { + browser.updatePhotoSkitch(at: 2, skitches: [["_id": "124", "num": 3, "type": "point", "coordinate": points3]], versionID: "zzz") + } } - + func dismissPhotoBrowser(_ photoBrowser: PhotoBrowser) { dismissPhotoBrowser(toView: imageView) } - + func photoBrowser(_ browser: PhotoBrowser, longPressOnPhoto photo: Photo, index: Int) { let alertController = UIAlertController.init(title: nil, message: nil, preferredStyle: UIAlertController.Style.actionSheet) let cancelAction = UIAlertAction.init(title: "Cancel", style: UIAlertAction.Style.cancel, handler: nil)