Skip to content
This repository has been archived by the owner on Feb 8, 2023. It is now read-only.

Commit

Permalink
Merge pull request #15 from teambition/feature/high-resolution-image
Browse files Browse the repository at this point in the history
fix memory issues when display image with high resolution
  • Loading branch information
D-Wang authored May 21, 2019
2 parents a54a5ee + 426ca74 commit 167b547
Show file tree
Hide file tree
Showing 28 changed files with 539 additions and 323 deletions.
4 changes: 1 addition & 3 deletions Cartfile
Original file line number Diff line number Diff line change
@@ -1,3 +1 @@
github "onevcat/Kingfisher"
github "Quick/Nimble"
github "Quick/Quick"
github "onevcat/Kingfisher" ~> 4.0.0
2 changes: 2 additions & 0 deletions Cartfile.private
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
github "Quick/Nimble" ~> 7.3.0
github "Quick/Quick" ~> 1.3.0
12 changes: 12 additions & 0 deletions PhotoBrowser.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -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 */; };
Expand Down Expand Up @@ -60,6 +63,9 @@
4A52D22F1C72F47C001C257B /* Photo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Photo.swift; sourceTree = "<group>"; };
4A52D2371C72F568001C257B /* PhotoPreviewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PhotoPreviewController.swift; sourceTree = "<group>"; };
4A6BC7C01C770F6400DACDA5 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = "<group>"; };
4A70A002225308A100DBD070 /* Kingfisher+CacheSerializer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Kingfisher+CacheSerializer.swift"; sourceTree = "<group>"; };
4A7E8F3D224A16A6005B54C8 /* ImageContainerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageContainerView.swift; sourceTree = "<group>"; };
4A8A90952244C27D00A09C55 /* HighResolutionImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HighResolutionImageView.swift; sourceTree = "<group>"; };
4A8EB7281CEF16020065EAB0 /* PBActionBarItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PBActionBarItem.swift; sourceTree = "<group>"; };
864F45E71E9F0A4900FF9215 /* Skitch.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Skitch.swift; sourceTree = "<group>"; };
86AEF72F1EDEC8A000034DBA /* SkitchView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SkitchView.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -155,6 +161,8 @@
C4B64B4E2117E75500B29C5B /* PBToolbar.swift */,
C4B64B502117E77200B29C5B /* PBNavigationBar.swift */,
C4B64B522117E7B000B29C5B /* WaitingView.swift */,
4A8A90952244C27D00A09C55 /* HighResolutionImageView.swift */,
4A7E8F3D224A16A6005B54C8 /* ImageContainerView.swift */,
);
path = CustomView;
sourceTree = "<group>";
Expand All @@ -173,6 +181,7 @@
children = (
C4C79EA02057808200C92C0C /* UIView+Frame.swift */,
C4B64B622117E95300B29C5B /* UIViewController+Animation.swift */,
4A70A002225308A100DBD070 /* Kingfisher+CacheSerializer.swift */,
);
path = Extensions;
sourceTree = "<group>";
Expand Down Expand Up @@ -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 */,
Expand All @@ -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 */,
Expand Down
Binary file not shown.
2 changes: 2 additions & 0 deletions PhotoBrowser/Animation/DismissAnimation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
Expand Down
69 changes: 69 additions & 0 deletions PhotoBrowser/CustomView/HighResolutionImageView.swift
Original file line number Diff line number Diff line change
@@ -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)
}
}
79 changes: 79 additions & 0 deletions PhotoBrowser/CustomView/ImageContainerView.swift
Original file line number Diff line number Diff line change
@@ -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
}
}
}
43 changes: 43 additions & 0 deletions PhotoBrowser/Extensions/Kingfisher+CacheSerializer.swift
Original file line number Diff line number Diff line change
@@ -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>.image(data: data,
options: imageCreatingOptions)
}
}
19 changes: 8 additions & 11 deletions PhotoBrowser/PhotoBrowser.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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
}
Expand All @@ -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()
}
}
}
Expand Down Expand Up @@ -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: {
Expand All @@ -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
}
Expand Down
Loading

0 comments on commit 167b547

Please sign in to comment.