From 604a4c61a5ab59a1f002f73eb4406e7fbe7ad247 Mon Sep 17 00:00:00 2001 From: Vitalii Parovishnyk Date: Tue, 21 Feb 2017 12:39:21 +0700 Subject: [PATCH] Code review --- IGRPhotoTweaks.podspec | 2 +- IGRPhotoTweaks.xcodeproj/project.pbxproj | 34 ++- .../{ => Category}/UIColor+Tweak.swift | 0 .../CropView/IGRCropCornerView.swift | 10 +- IGRPhotoTweaks/CropView/IGRCropView.swift | 173 ++++++++++------ .../CustomViews/IGRPhotoScrollView.swift | 41 +++- IGRPhotoTweaks/IGRPhotoTweakView.swift | 194 +++++++++++------- .../IGRPhotoTweakViewController.swift | 47 +++-- Settings/Info.plist | 2 +- 9 files changed, 324 insertions(+), 179 deletions(-) rename IGRPhotoTweaks/{ => Category}/UIColor+Tweak.swift (100%) diff --git a/IGRPhotoTweaks.podspec b/IGRPhotoTweaks.podspec index 5a91232..0beb20d 100755 --- a/IGRPhotoTweaks.podspec +++ b/IGRPhotoTweaks.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |spec| spec.name = 'IGRPhotoTweaks' - spec.version = '1.0.3' + spec.version = '1.0.4' spec.platform = :ios, '9.0' spec.license = { :type => "MIT", :file => "LICENSE" } diff --git a/IGRPhotoTweaks.xcodeproj/project.pbxproj b/IGRPhotoTweaks.xcodeproj/project.pbxproj index eafde90..a995122 100644 --- a/IGRPhotoTweaks.xcodeproj/project.pbxproj +++ b/IGRPhotoTweaks.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + A5F746251E5C093900B02B9D /* UIColor+Tweak.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5F746241E5C093900B02B9D /* UIColor+Tweak.swift */; }; A5FE197A1E4F8D5700EA6F73 /* IGRPhotoTweaks.h in Headers */ = {isa = PBXBuildFile; fileRef = A5FE19781E4F8D5700EA6F73 /* IGRPhotoTweaks.h */; settings = {ATTRIBUTES = (Public, ); }; }; A5FE197E1E4F8DC600EA6F73 /* IGRCropCornerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A51E64E51E4A2DFF00CD5872 /* IGRCropCornerView.swift */; }; A5FE197F1E4F8DC600EA6F73 /* IGRCropView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A51E64E61E4A2DFF00CD5872 /* IGRCropView.swift */; }; @@ -19,7 +20,6 @@ A5FE19871E4F8DC600EA6F73 /* IGRPhotoTweakViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5DFB1E71E483D35009ACD31 /* IGRPhotoTweakViewController.swift */; }; A5FE19881E4F8DC600EA6F73 /* IGRPhotoTweakView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5DFB1DB1E4822B7009ACD31 /* IGRPhotoTweakView.swift */; }; A5FE19891E4F8DC600EA6F73 /* IGRPhotoTweakConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5DFB1E31E482801009ACD31 /* IGRPhotoTweakConstants.swift */; }; - A5FE198A1E4F8DC600EA6F73 /* UIColor+Tweak.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5DFB1D91E482216009ACD31 /* UIColor+Tweak.swift */; }; A5FE198B1E4F8DC600EA6F73 /* IGRRadianAngle.swift in Sources */ = {isa = PBXBuildFile; fileRef = A51E64F91E4AD94E00CD5872 /* IGRRadianAngle.swift */; }; /* End PBXBuildFile section */ @@ -37,10 +37,10 @@ A5DFB1CE1E481FE4009ACD31 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; A5DFB1D01E481FE4009ACD31 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; A5DFB1D21E481FE4009ACD31 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - A5DFB1D91E482216009ACD31 /* UIColor+Tweak.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIColor+Tweak.swift"; sourceTree = ""; }; A5DFB1DB1E4822B7009ACD31 /* IGRPhotoTweakView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IGRPhotoTweakView.swift; sourceTree = ""; }; A5DFB1E31E482801009ACD31 /* IGRPhotoTweakConstants.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IGRPhotoTweakConstants.swift; sourceTree = ""; }; A5DFB1E71E483D35009ACD31 /* IGRPhotoTweakViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IGRPhotoTweakViewController.swift; sourceTree = ""; }; + A5F746241E5C093900B02B9D /* UIColor+Tweak.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIColor+Tweak.swift"; sourceTree = ""; }; A5FE19761E4F8D5600EA6F73 /* IGRPhotoTweaks.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = IGRPhotoTweaks.framework; sourceTree = BUILT_PRODUCTS_DIR; }; A5FE19781E4F8D5700EA6F73 /* IGRPhotoTweaks.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = IGRPhotoTweaks.h; sourceTree = ""; }; /* End PBXFileReference section */ @@ -99,12 +99,12 @@ A5DFB1CA1E481FE4009ACD31 /* IGRPhotoTweaks */ = { isa = PBXGroup; children = ( + A5F746231E5C093900B02B9D /* Category */, A51E64E41E4A2DFF00CD5872 /* CropView */, A51E64E81E4A2DFF00CD5872 /* CustomViews */, A5DFB1E71E483D35009ACD31 /* IGRPhotoTweakViewController.swift */, A5DFB1DB1E4822B7009ACD31 /* IGRPhotoTweakView.swift */, A5DFB1E31E482801009ACD31 /* IGRPhotoTweakConstants.swift */, - A5DFB1D91E482216009ACD31 /* UIColor+Tweak.swift */, A51E64F91E4AD94E00CD5872 /* IGRRadianAngle.swift */, A5FE19781E4F8D5700EA6F73 /* IGRPhotoTweaks.h */, ); @@ -129,6 +129,14 @@ path = Settings; sourceTree = ""; }; + A5F746231E5C093900B02B9D /* Category */ = { + isa = PBXGroup; + children = ( + A5F746241E5C093900B02B9D /* UIColor+Tweak.swift */, + ); + path = Category; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ @@ -214,6 +222,7 @@ files = ( A5FE197E1E4F8DC600EA6F73 /* IGRCropCornerView.swift in Sources */, A5FE197F1E4F8DC600EA6F73 /* IGRCropView.swift in Sources */, + A5F746251E5C093900B02B9D /* UIColor+Tweak.swift in Sources */, A5FE19801E4F8DC600EA6F73 /* IGRPhotoContentView.swift in Sources */, A5FE19811E4F8DC600EA6F73 /* IGRCropLine.swift in Sources */, A5FE19821E4F8DC600EA6F73 /* IGRCropGridLine.swift in Sources */, @@ -223,7 +232,6 @@ A5FE19871E4F8DC600EA6F73 /* IGRPhotoTweakViewController.swift in Sources */, A5FE19881E4F8DC600EA6F73 /* IGRPhotoTweakView.swift in Sources */, A5FE19891E4F8DC600EA6F73 /* IGRPhotoTweakConstants.swift in Sources */, - A5FE198A1E4F8DC600EA6F73 /* UIColor+Tweak.swift in Sources */, A5FE198B1E4F8DC600EA6F73 /* IGRRadianAngle.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -255,8 +263,6 @@ buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BOOL_CONVERSION = YES; @@ -271,12 +277,10 @@ CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; @@ -306,8 +310,6 @@ buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BOOL_CONVERSION = YES; @@ -322,12 +324,10 @@ CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; @@ -348,10 +348,8 @@ A5FE197C1E4F8D5700EA6F73 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { - CODE_SIGN_IDENTITY = ""; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 104; DEFINES_MODULE = YES; - DEVELOPMENT_TEAM = DMP42GVPJ3; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; @@ -362,17 +360,14 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; VERSIONING_SYSTEM = "apple-generic"; - VERSION_INFO_PREFIX = ""; }; name = Debug; }; A5FE197D1E4F8D5700EA6F73 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { - CODE_SIGN_IDENTITY = ""; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 104; DEFINES_MODULE = YES; - DEVELOPMENT_TEAM = DMP42GVPJ3; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; @@ -383,7 +378,6 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; VERSIONING_SYSTEM = "apple-generic"; - VERSION_INFO_PREFIX = ""; }; name = Release; }; diff --git a/IGRPhotoTweaks/UIColor+Tweak.swift b/IGRPhotoTweaks/Category/UIColor+Tweak.swift similarity index 100% rename from IGRPhotoTweaks/UIColor+Tweak.swift rename to IGRPhotoTweaks/Category/UIColor+Tweak.swift diff --git a/IGRPhotoTweaks/CropView/IGRCropCornerView.swift b/IGRPhotoTweaks/CropView/IGRCropCornerView.swift index 84bc3f9..06466db 100644 --- a/IGRPhotoTweaks/CropView/IGRCropCornerView.swift +++ b/IGRPhotoTweaks/CropView/IGRCropCornerView.swift @@ -28,10 +28,16 @@ import UIKit fileprivate func setup(cornerType type: CropCornerType, lineWidth: CGFloat, lineLenght: CGFloat) { - let horizontal = IGRCropCornerLine(frame: CGRect(x: 0.0, y: 0.0, width: lineLenght, height: lineWidth)) + let horizontal = IGRCropCornerLine(frame: CGRect(x: 0.0, + y: 0.0, + width: lineLenght, + height: lineWidth)) self.addSubview(horizontal) - let vertical = IGRCropCornerLine(frame: CGRect(x: 0.0, y: 0.0, width: lineWidth, height: lineLenght)) + let vertical = IGRCropCornerLine(frame: CGRect(x: 0.0, + y: 0.0, + width: lineWidth, + height: lineLenght)) self.addSubview(vertical) if type == .upperLeft { diff --git a/IGRPhotoTweaks/CropView/IGRCropView.swift b/IGRPhotoTweaks/CropView/IGRCropView.swift index b1c02bd..b450571 100644 --- a/IGRPhotoTweaks/CropView/IGRCropView.swift +++ b/IGRPhotoTweaks/CropView/IGRCropView.swift @@ -9,41 +9,55 @@ import UIKit protocol IGRCropViewDelegate: NSObjectProtocol { - + /* + Calls ones, when user start interaction with view + */ func cropViewDidStartCrop(_ cropView: IGRCropView) + /* + Calls always, when user move touch around view + */ func cropViewDidMove(_ cropView: IGRCropView) + /* + Calls ones, when user stop interaction with view + */ func cropViewDidStopCrop(_ cropView: IGRCropView) } class IGRCropView: UIView { - override class func initialize () { - self.appearance().backgroundColor = UIColor.clear - } + //MARK: - Public VARs - var upperLeft: IGRCropCornerView! - var upperRight: IGRCropCornerView! - var lowerRight: IGRCropCornerView! - var lowerLeft: IGRCropCornerView! + /* + The optional View Delegate. + */ - var horizontalCropLines = [UIView]() - var verticalCropLines = [UIView]() + weak var delegate: IGRCropViewDelegate? - var horizontalGridLines = [UIView]() - var verticalGridLines = [UIView]() + //MARK: - Private VARs - var cornerBorderLength = kCropViewCornerLength - var cornerBorderWidth = kCropViewCornerWidth + fileprivate var upperLeft: IGRCropCornerView! + fileprivate var upperRight: IGRCropCornerView! + fileprivate var lowerRight: IGRCropCornerView! + fileprivate var lowerLeft: IGRCropCornerView! - weak var delegate: IGRCropViewDelegate? + fileprivate var horizontalCropLines = [UIView]() + fileprivate var verticalCropLines = [UIView]() - var isCropLinesDismissed: Bool = false - var isGridLinesDismissed: Bool = false + fileprivate var horizontalGridLines = [UIView]() + fileprivate var verticalGridLines = [UIView]() - static func distanceBetweenPoints(point0: CGPoint, point1: CGPoint) -> CGFloat { - return sqrt(pow(point1.x - point0.x, 2) + pow(point1.y - point0.y, 2)) + fileprivate var cornerBorderLength = kCropViewCornerLength + fileprivate var cornerBorderWidth = kCropViewCornerWidth + + private(set) var isCropLinesDismissed: Bool = false + private(set) var isGridLinesDismissed: Bool = false + + // MARK: - Life Cicle + + override class func initialize () { + self.appearance().backgroundColor = UIColor.clear } init(frame: CGRect, cornerBorderWidth: CGFloat, cornerBorderLength:CGFloat) { @@ -62,22 +76,22 @@ class IGRCropView: UIView { } fileprivate func setup() { - for _ in 0.., with event: UIEvent?) { if touches.count == 1 { self.updateCropLines(false) @@ -123,17 +147,21 @@ class IGRCropView: UIView { if touches.count == 1 { let location: CGPoint = (touches.first?.location(in: self))! var frame: CGRect = self.frame + let p0 = CGPoint(x: 0.0, y: 0.0) let p1 = CGPoint(x: self.frame.size.width, y: 0.0) let p2 = CGPoint(x: 0.0, y: self.frame.size.height) let p3 = CGPoint(x: self.frame.size.width, y: self.frame.size.height) + let canChangeWidth: Bool = frame.size.width > kMinimumCropArea let canChangeHeight: Bool = frame.size.height > kMinimumCropArea + if IGRCropView.distanceBetweenPoints(point0: location, point1: p0) < kCropViewHotArea { if canChangeWidth { frame.origin.x += location.x frame.size.width -= location.x } + if canChangeHeight { frame.origin.y += location.y frame.size.height -= location.y @@ -143,6 +171,7 @@ class IGRCropView: UIView { if canChangeWidth { frame.size.width = location.x } + if canChangeHeight { frame.origin.y += location.y frame.size.height -= location.y @@ -153,6 +182,7 @@ class IGRCropView: UIView { frame.origin.x += location.x frame.size.width -= location.x } + if canChangeHeight { frame.size.height = location.y } @@ -161,6 +191,7 @@ class IGRCropView: UIView { if canChangeWidth { frame.size.width = location.x } + if canChangeHeight { frame.size.height = location.y } @@ -189,6 +220,7 @@ class IGRCropView: UIView { } self.frame = frame + // update crop lines self.updateCropLines(false) @@ -204,15 +236,19 @@ class IGRCropView: UIView { self.delegate?.cropViewDidStopCrop(self) } + //MARK: - Crop Lines + func updateCropLines(_ animate: Bool) { // show crop lines if self.isCropLinesDismissed { self.showCropLines() } + let animationBlock: ((_: Void) -> Void)? = {(_: Void) -> Void in self.update(self.horizontalCropLines, horizontal: true) self.update(self.verticalCropLines, horizontal: false) } + if animate { UIView.animate(withDuration: 0.25, animations: animationBlock!) } @@ -221,15 +257,36 @@ class IGRCropView: UIView { } } + func dismissCropLines() { + UIView.animate(withDuration: 0.2, animations: {() -> Void in + self.dismiss(self.horizontalCropLines) + self.dismiss(self.verticalCropLines) + }, completion: {(_ finished: Bool) -> Void in + self.isCropLinesDismissed = true + }) + } + + fileprivate func showCropLines() { + self.isCropLinesDismissed = false + UIView.animate(withDuration: 0.2, animations: {() -> Void in + self.show(self.horizontalCropLines) + self.show(self.verticalCropLines) + }) + } + + //MARK: - Crid Lines + func updateGridLines(_ animate: Bool) { // show grid lines if self.isGridLinesDismissed { self.showGridLines() } + let animationBlock: ((_: Void) -> Void)? = {(_: Void) -> Void in self.update(self.horizontalGridLines, horizontal: true) self.update(self.verticalGridLines, horizontal: false) } + if animate { UIView.animate(withDuration: 0.25, animations: animationBlock!) } @@ -238,14 +295,34 @@ class IGRCropView: UIView { } } + func dismissGridLines() { + UIView.animate(withDuration: 0.2, animations: {() -> Void in + self.dismiss(self.horizontalGridLines) + self.dismiss(self.verticalGridLines) + }, completion: {(_ finished: Bool) -> Void in + self.isGridLinesDismissed = true + }) + } + + fileprivate func showGridLines() { + self.isGridLinesDismissed = false + UIView.animate(withDuration: 0.2, animations: {() -> Void in + self.show(self.horizontalGridLines) + self.show(self.verticalGridLines) + }) + } + + //MARK: - Aspect Ratio + open func setCropAspectRect(aspect: String, maxSize: CGSize) { - let elements = aspect.components(separatedBy: ":"); + let elements = aspect.components(separatedBy: ":") let width: CGFloat = CGFloat(Float(elements.first!)!) let height: CGFloat = CGFloat(Float(elements.last!)!) var size = maxSize let mW = size.width / width let mH = size.height / height + if (mH < mW) { size.width = size.height / height * width } @@ -259,6 +336,8 @@ class IGRCropView: UIView { self.frame = CGRect(x:x, y:y, width: size.width, height: size.height) } + //MARK: - Private Lines funcs + fileprivate func update(_ lines: [UIView], horizontal: Bool) { let count = lines.count for (idx, line) in lines.enumerated() { @@ -277,49 +356,19 @@ class IGRCropView: UIView { } } - func dismissCropLines() { - UIView.animate(withDuration: 0.2, animations: {() -> Void in - self.dismiss(self.horizontalCropLines) - self.dismiss(self.verticalCropLines) - }, completion: {(_ finished: Bool) -> Void in - self.isCropLinesDismissed = true - }) - } - - func dismissGridLines() { - UIView.animate(withDuration: 0.2, animations: {() -> Void in - self.dismiss(self.horizontalGridLines) - self.dismiss(self.verticalGridLines) - }, completion: {(_ finished: Bool) -> Void in - self.isGridLinesDismissed = true - }) - } - fileprivate func dismiss(_ lines: [UIView]) { for (_, line) in lines.enumerated() { line.alpha = 0.0 } } - fileprivate func showCropLines() { - self.isCropLinesDismissed = false - UIView.animate(withDuration: 0.2, animations: {() -> Void in - self.show(self.horizontalCropLines) - self.show(self.verticalCropLines) - }) - } - - fileprivate func showGridLines() { - self.isGridLinesDismissed = false - UIView.animate(withDuration: 0.2, animations: {() -> Void in - self.show(self.horizontalGridLines) - self.show(self.verticalGridLines) - }) - } - fileprivate func show(_ lines: [UIView]) { for (_, line) in lines.enumerated() { line.alpha = 1.0 } } + + fileprivate static func distanceBetweenPoints(point0: CGPoint, point1: CGPoint) -> CGFloat { + return sqrt(pow(point1.x - point0.x, 2) + pow(point1.y - point0.y, 2)) + } } diff --git a/IGRPhotoTweaks/CustomViews/IGRPhotoScrollView.swift b/IGRPhotoTweaks/CustomViews/IGRPhotoScrollView.swift index 358153e..79ebe01 100644 --- a/IGRPhotoTweaks/CustomViews/IGRPhotoScrollView.swift +++ b/IGRPhotoTweaks/CustomViews/IGRPhotoScrollView.swift @@ -9,16 +9,37 @@ import UIKit protocol IGRPhotoScrollViewDelegate: NSObjectProtocol { + /* + Calls ones, when user start interaction with view + */ func scrollViewDidStartUpdateScrollContentOffset(_ scrollView: UIScrollView) + + /* + Calls ones, when user stop interaction with view + */ func scrollViewDidStopScrollUpdateContentOffset(_ scrollView: UIScrollView) } class IGRPhotoScrollView: UIScrollView { + + //MARK: - Public VARs + + /* + View for func viewForZooming(in scrollView: UIScrollView) + */ var photoContentView: IGRPhotoContentView! + /* + The optional scroll delegate. + */ weak var updateDelegate: IGRPhotoScrollViewDelegate? + + //MARK: - Protected VARs + fileprivate var isUpdatingContentOffset = false + //MARK: - Content Offsets + func setContentOffsetY(_ offsetY: CGFloat) { var contentOffset: CGPoint = self.contentOffset contentOffset.y = offsetY @@ -41,7 +62,9 @@ class IGRPhotoScrollView: UIScrollView { } let selector = #selector(self.stopUpdateContentOffset) - IGRPhotoScrollView.cancelPreviousPerformRequests(withTarget: self, selector: selector, object: nil) + IGRPhotoScrollView.cancelPreviousPerformRequests(withTarget: self, + selector: selector, + object: nil) perform(selector, with: nil, afterDelay: 0.5) } get { @@ -49,17 +72,19 @@ class IGRPhotoScrollView: UIScrollView { } } - func zoomScaleToBound() -> CGFloat { - let scaleW: CGFloat = self.bounds.size.width / self.photoContentView.bounds.size.width - let scaleH: CGFloat = self.bounds.size.height / self.photoContentView.bounds.size.height - - return max(scaleW, scaleH) - } - func stopUpdateContentOffset() { if (isUpdatingContentOffset && updateDelegate != nil) { isUpdatingContentOffset = false updateDelegate?.scrollViewDidStopScrollUpdateContentOffset(self) } } + + //MARK: - Zoom + + func zoomScaleToBound() -> CGFloat { + let scaleW: CGFloat = self.bounds.size.width / self.photoContentView.bounds.size.width + let scaleH: CGFloat = self.bounds.size.height / self.photoContentView.bounds.size.height + + return max(scaleW, scaleH) + } } diff --git a/IGRPhotoTweaks/IGRPhotoTweakView.swift b/IGRPhotoTweaks/IGRPhotoTweakView.swift index c8a8ee0..6890c44 100644 --- a/IGRPhotoTweaks/IGRPhotoTweakView.swift +++ b/IGRPhotoTweaks/IGRPhotoTweakView.swift @@ -9,24 +9,36 @@ import UIKit @objc public protocol IGRPhotoTweakViewCustomizationDelegate: NSObjectProtocol { + /* + Lines between mask and crop area + */ func borderColor() -> UIColor func borderWidth() -> CGFloat + /* + Corner of 2 border lines + */ func cornerBorderWidth() -> CGFloat func cornerBorderLength() -> CGFloat + /* + Mask customization + */ func isHighlightMask() -> Bool + func highlightMaskAlphaValue() -> CGFloat + /* + Top offset for crop view + */ func canvasHeaderHeigth() -> CGFloat } @objc public class IGRPhotoTweakView: UIView { - override public class func initialize () { - self.appearance().backgroundColor = UIColor.photoTweakCanvasBackground() - } + + //MARK: - Public VARs open weak var customizationDelegate: IGRPhotoTweakViewCustomizationDelegate? @@ -37,7 +49,8 @@ import UIKit var photoTranslation: CGPoint { get { - let rect: CGRect = self.photoContentView.convert(self.photoContentView.bounds, to: self) + let rect: CGRect = self.photoContentView.convert(self.photoContentView.bounds, + to: self) let point = CGPoint(x: (rect.origin.x + rect.size.width / 2.0), y: (rect.origin.y + rect.size.height / 2.0)) let zeroPoint = CGPoint(x: (self.frame.width / 2.0), y: self.centerY) @@ -46,6 +59,8 @@ import UIKit } } + //MARK: - Private VARs + fileprivate var scrollView: IGRPhotoScrollView! fileprivate var image: UIImage! @@ -65,6 +80,12 @@ import UIKit fileprivate var centerY: CGFloat! fileprivate var originalPoint: CGPoint! + // MARK: - Life Cicle + + override public class func initialize () { + self.appearance().backgroundColor = UIColor.photoTweakCanvasBackground() + } + init(frame: CGRect, image: UIImage, customizationDelegate: IGRPhotoTweakViewCustomizationDelegate!) { super.init(frame: frame) @@ -124,6 +145,10 @@ import UIKit self.originalPoint = self.convert(self.scrollView.center, to: self) } + required public init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + public override func layoutSubviews() { super.layoutSubviews() @@ -138,25 +163,22 @@ import UIKit } } - func maxBounds() -> CGRect { - // scale the image - self.maximumCanvasSize = CGSize(width: (kMaximumCanvasWidthRatio * self.frame.size.width), - height: (kMaximumCanvasHeightRatio * self.frame.size.height - self.canvasHeaderHeigth())) - - self.centerY = self.maximumCanvasSize.height / 2.0 + self.canvasHeaderHeigth() - - let scaleX: CGFloat = self.image.size.width / self.maximumCanvasSize.width - let scaleY: CGFloat = self.image.size.height / self.maximumCanvasSize.height - let scale: CGFloat = max(scaleX, scaleY) - - let bounds = CGRect(x: 0.0, - y: 0.0, - width: (self.image.size.width / scale), - height: (self.image.size.height / scale)) + // MARK: - Touches + + override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + if self.cropView.frame.insetBy(dx: -kCropViewHotArea, + dy: -kCropViewHotArea).contains(point) && + !self.cropView.frame.insetBy(dx: kCropViewHotArea, + dy: kCropViewHotArea).contains(point) { + + return self.cropView + } - return bounds + return self.scrollView } + // MARK: - Customization + func borderColor() -> UIColor { return (self.customizationDelegate?.borderColor())! } @@ -185,19 +207,7 @@ import UIKit return (self.customizationDelegate?.canvasHeaderHeigth())! } - required public init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { - if self.cropView.frame.insetBy(dx: -kCropViewHotArea, dy: -kCropViewHotArea).contains(point) && - !self.cropView.frame.insetBy(dx: kCropViewHotArea, dy: kCropViewHotArea).contains(point) { - - return self.cropView - } - - return self.scrollView - } + //MARK: - Masks fileprivate func updateMasks(_ animate: Bool) { let animationBlock: ((_: Void) -> Void)? = {(_: Void) -> Void in @@ -218,6 +228,7 @@ import UIKit width: (self.frame.size.width - (self.cropView.frame.origin.x + self.cropView.frame.size.width)), height: (self.cropView.frame.origin.y + self.cropView.frame.size.height)) } + if animate { UIView.animate(withDuration: 0.25, animations: animationBlock!) } @@ -246,22 +257,12 @@ import UIKit } } - fileprivate func checkScrollViewContentOffset() { - self.scrollView.setContentOffsetX(max(self.scrollView.contentOffset.x, 0)) - self.scrollView.setContentOffsetY(max(self.scrollView.contentOffset.y, 0)) - - if self.scrollView.contentSize.height - self.scrollView.contentOffset.y <= self.scrollView.bounds.size.height { - self.scrollView.setContentOffsetY(self.scrollView.contentSize.height - self.scrollView.bounds.size.height) - } - - if self.scrollView.contentSize.width - self.scrollView.contentOffset.x <= self.scrollView.bounds.size.width { - self.scrollView.setContentOffsetX(self.scrollView.contentSize.width - self.scrollView.bounds.size.width) - } - } + //MARK: - Angle - open func changedAngel(value: CGFloat) { + open func changedAngle(value: CGFloat) { // update masks self.updateMasks(false) + self.highlightMask(true, animate: false); // update grids self.cropView.updateGridLines(false) @@ -273,36 +274,13 @@ import UIKit self.updatePosition() } - fileprivate func updatePosition() { - // position scroll view - let width: CGFloat = fabs(cos(self.angle)) * self.cropView.frame.size.width + fabs(sin(self.angle)) * self.cropView.frame.size.height - let height: CGFloat = fabs(sin(self.angle)) * self.cropView.frame.size.width + fabs(cos(self.angle)) * self.cropView.frame.size.height - let center: CGPoint = self.scrollView.center - let contentOffset: CGPoint = self.scrollView.contentOffset - let contentOffsetCenter = CGPoint(x: (contentOffset.x + self.scrollView.bounds.size.width / 2.0), - y: (contentOffset.y + self.scrollView.bounds.size.height / 2.0)) - self.scrollView.bounds = CGRect(x: 0.0, y: 0.0, width: width, height: height) - let newContentOffset = CGPoint(x: (contentOffsetCenter.x - self.scrollView.bounds.size.width / 2.0), - y: (contentOffsetCenter.y - self.scrollView.bounds.size.height / 2.0)) - self.scrollView.contentOffset = newContentOffset - self.scrollView.center = center - - // scale scroll view - let shouldScale: Bool = self.scrollView.contentSize.width / self.scrollView.bounds.size.width <= 1.0 || - self.scrollView.contentSize.height / self.scrollView.bounds.size.height <= 1.0 - if !self.manualZoomed || shouldScale { - let zoom = self.scrollView.zoomScaleToBound() - self.scrollView.setZoomScale(zoom, animated: false) - self.scrollView.minimumZoomScale = zoom - self.manualZoomed = false - } - self.checkScrollViewContentOffset() - } - - open func stopChangeAngel() { + open func stopChangeAngle() { self.cropView.dismissGridLines() + self.highlightMask(false, animate: false); } + //MARK: - Reset + open func resetView() { UIView.animate(withDuration: 0.25, animations: {() -> Void in self.angle = 0 @@ -321,6 +299,8 @@ import UIKit }) } + //MARK: - Aspect Ratio + open func resetAspectRect() { self.cropView.frame = CGRect(x: 0.0, y: 0.0, @@ -341,8 +321,70 @@ import UIKit self.updateMasks(false) } + + //MARK: - Private FUNCs + + fileprivate func maxBounds() -> CGRect { + // scale the image + self.maximumCanvasSize = CGSize(width: (kMaximumCanvasWidthRatio * self.frame.size.width), + height: (kMaximumCanvasHeightRatio * self.frame.size.height - self.canvasHeaderHeigth())) + + self.centerY = self.maximumCanvasSize.height / 2.0 + self.canvasHeaderHeigth() + + let scaleX: CGFloat = self.image.size.width / self.maximumCanvasSize.width + let scaleY: CGFloat = self.image.size.height / self.maximumCanvasSize.height + let scale: CGFloat = max(scaleX, scaleY) + + let bounds = CGRect(x: 0.0, + y: 0.0, + width: (self.image.size.width / scale), + height: (self.image.size.height / scale)) + + return bounds + } + + fileprivate func checkScrollViewContentOffset() { + self.scrollView.setContentOffsetX(max(self.scrollView.contentOffset.x, 0)) + self.scrollView.setContentOffsetY(max(self.scrollView.contentOffset.y, 0)) + + if self.scrollView.contentSize.height - self.scrollView.contentOffset.y <= self.scrollView.bounds.size.height { + self.scrollView.setContentOffsetY(self.scrollView.contentSize.height - self.scrollView.bounds.size.height) + } + + if self.scrollView.contentSize.width - self.scrollView.contentOffset.x <= self.scrollView.bounds.size.width { + self.scrollView.setContentOffsetX(self.scrollView.contentSize.width - self.scrollView.bounds.size.width) + } + } + + fileprivate func updatePosition() { + // position scroll view + let width: CGFloat = fabs(cos(self.angle)) * self.cropView.frame.size.width + fabs(sin(self.angle)) * self.cropView.frame.size.height + let height: CGFloat = fabs(sin(self.angle)) * self.cropView.frame.size.width + fabs(cos(self.angle)) * self.cropView.frame.size.height + let center: CGPoint = self.scrollView.center + let contentOffset: CGPoint = self.scrollView.contentOffset + let contentOffsetCenter = CGPoint(x: (contentOffset.x + self.scrollView.bounds.size.width / 2.0), + y: (contentOffset.y + self.scrollView.bounds.size.height / 2.0)) + self.scrollView.bounds = CGRect(x: 0.0, y: 0.0, width: width, height: height) + let newContentOffset = CGPoint(x: (contentOffsetCenter.x - self.scrollView.bounds.size.width / 2.0), + y: (contentOffsetCenter.y - self.scrollView.bounds.size.height / 2.0)) + self.scrollView.contentOffset = newContentOffset + self.scrollView.center = center + + // scale scroll view + let shouldScale: Bool = self.scrollView.contentSize.width / self.scrollView.bounds.size.width <= 1.0 || + self.scrollView.contentSize.height / self.scrollView.bounds.size.height <= 1.0 + if !self.manualZoomed || shouldScale { + let zoom = self.scrollView.zoomScaleToBound() + self.scrollView.setZoomScale(zoom, animated: false) + self.scrollView.minimumZoomScale = zoom + self.manualZoomed = false + } + self.checkScrollViewContentOffset() + } } +//MARK: - Delegats funcs + extension IGRPhotoTweakView : UIScrollViewDelegate { public func viewForZooming(in scrollView: UIScrollView) -> UIView? { return self.photoContentView @@ -383,14 +425,17 @@ extension IGRPhotoTweakView : IGRCropViewDelegate { let scaleX: CGFloat = self.originalSize.width / cropView.bounds.size.width let scaleY: CGFloat = self.originalSize.height / cropView.bounds.size.height let scale: CGFloat = min(scaleX, scaleY) + // calculate the new bounds of crop view let newCropBounds = CGRect(x: 0.0, y: 0.0, width: (scale * cropView.frame.size.width), height: (scale * cropView.frame.size.height)) + // calculate the new bounds of scroll view let width: CGFloat = fabs(cos(self.angle)) * newCropBounds.size.width + fabs(sin(self.angle)) * newCropBounds.size.height let height: CGFloat = fabs(sin(self.angle)) * newCropBounds.size.width + fabs(cos(self.angle)) * newCropBounds.size.height + // calculate the zoom area of scroll view var scaleFrame: CGRect = cropView.frame if scaleFrame.size.width >= self.scrollView.bounds.size.width { @@ -425,13 +470,16 @@ extension IGRPhotoTweakView : IGRCropViewDelegate { }) self.manualZoomed = true + // update masks self.updateMasks(true) self.cropView.dismissCropLines() self.cropView.dismissGridLines() + let scaleH: CGFloat = self.scrollView.bounds.size.height / self.scrollView.contentSize.height let scaleW: CGFloat = self.scrollView.bounds.size.width / self.scrollView.contentSize.width var scaleM: CGFloat = max(scaleH, scaleW) + let duration = 0.2 DispatchQueue.main.asyncAfter(deadline: .now() + duration, execute: {() -> Void in if scaleM > 1 { diff --git a/IGRPhotoTweaks/IGRPhotoTweakViewController.swift b/IGRPhotoTweaks/IGRPhotoTweakViewController.swift index aa4c2d9..f9ebb12 100644 --- a/IGRPhotoTweaks/IGRPhotoTweakViewController.swift +++ b/IGRPhotoTweaks/IGRPhotoTweakViewController.swift @@ -24,21 +24,28 @@ import Photos @objc(IGRPhotoTweakViewController) open class IGRPhotoTweakViewController: UIViewController { - /** + //MARK: - Public VARs + + /* Image to process. */ open var image: UIImage! - /** - Flag indicating whether the image cropped will be saved to photo library automatically. Defaults to YES. - */ - internal var isAutoSaveToLibray: Bool = false - - /** + + /* The optional photo tweaks controller delegate. */ open weak var delegate: IGRPhotoTweakViewControllerDelegate? - fileprivate var photoView: IGRPhotoTweakView!; + //MARK: - Protected VARs + + /* + Flag indicating whether the image cropped will be saved to photo library automatically. Defaults to YES. + */ + internal var isAutoSaveToLibray: Bool = false + + //MARK: - Private VARs + + fileprivate var photoView: IGRPhotoTweakView! fileprivate let kBitsPerComponent = 8 fileprivate let kBitmapBytesPerRow = 0 @@ -71,11 +78,11 @@ import Photos // MARK: - Public open func changedAngel(value: CGFloat) { - self.photoView.changedAngel(value: value) + self.photoView.changedAngle(value: value) } open func stopChangeAngel() { - self.photoView.stopChangeAngel() + self.photoView.stopChangeAngle() } open func resetView() { @@ -95,12 +102,16 @@ import Photos // rotate transform = transform.rotated(by: self.photoView.angle) // scale + let t: CGAffineTransform = self.photoView.photoContentView.transform let xScale: CGFloat = sqrt(t.a * t.a + t.c * t.c) let yScale: CGFloat = sqrt(t.b * t.b + t.d * t.d) transform = transform.scaledBy(x: xScale, y: yScale) + let imageRef: CGImage = self.newTransformedImage(transform, sourceImage: self.image.cgImage!, sourceSize: self.image.size, sourceOrientation: self.image.imageOrientation, outputWidth: self.image.size.width, cropSize: self.photoView.cropView.frame.size, imageViewSize: self.photoView.photoContentView.bounds.size) + let image = UIImage(cgImage: imageRef) + if self.isAutoSaveToLibray { let writePhotoToLibraryBlock: ((_: Void) -> Void)? = {(_: Void) -> Void in @@ -156,6 +167,7 @@ import Photos fileprivate func newScaledImage(_ source: CGImage, with orientation: UIImageOrientation, to size: CGSize, with quality: CGInterpolationQuality) -> CGImage { var srcSize: CGSize = size var rotation: CGFloat = 0.0 + switch orientation { case .up: rotation = 0 @@ -173,6 +185,7 @@ import Photos let rgbColorSpace: CGColorSpace? = CGColorSpaceCreateDeviceRGB() var bitmapInfo: CGBitmapInfo! + if #available(iOS 10.0, *) { bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedLast.rawValue | CGImageByteOrderInfo.order32Big.rawValue) } else { @@ -201,8 +214,10 @@ import Photos fileprivate func newTransformedImage(_ transform: CGAffineTransform, sourceImage: CGImage, sourceSize: CGSize, sourceOrientation: UIImageOrientation, outputWidth: CGFloat, cropSize: CGSize, imageViewSize: CGSize) -> CGImage { let source: CGImage = self.newScaledImage(sourceImage, with: sourceOrientation, to: sourceSize, with: .none) + let aspect: CGFloat = cropSize.height / cropSize.width let outputSize = CGSize(width: outputWidth, height: (outputWidth * aspect)) + let context = CGContext(data: nil, width: Int(outputSize.width), height: Int(outputSize.height), @@ -230,12 +245,18 @@ import Photos internal func image(image: UIImage, didFinishSavingWithError error: NSError?, contextInfo:UnsafeRawPointer) { if error == nil { - let ac = UIAlertController(title: "Save error", message: error?.localizedDescription, preferredStyle: .alert) - ac.addAction(UIAlertAction(title: "OK", style: .default, handler: nil)) + let ac = UIAlertController(title: "Save error", + message: error?.localizedDescription, + preferredStyle: .alert) + ac.addAction(UIAlertAction(title: "OK", + style: .default, + handler: nil)) present(ac, animated: true, completion: nil) } } + //MARK: - Customization + open func customBorderColor() -> UIColor { return UIColor.cropLine() } @@ -265,6 +286,8 @@ import Photos } } +//MARK: - Delegats funcs + extension IGRPhotoTweakViewController : IGRPhotoTweakViewCustomizationDelegate { public func borderColor() -> UIColor { return self.customBorderColor() diff --git a/Settings/Info.plist b/Settings/Info.plist index fbe1e6b..6a68363 100644 --- a/Settings/Info.plist +++ b/Settings/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 1.0 + 1.0.4 CFBundleVersion $(CURRENT_PROJECT_VERSION) NSPrincipalClass