diff --git a/LeadKit/.DS_Store b/LeadKit/.DS_Store index ff34d055..2b1134c4 100644 Binary files a/LeadKit/.DS_Store and b/LeadKit/.DS_Store differ diff --git a/LeadKit/LeadKit.xcodeproj/project.pbxproj b/LeadKit/LeadKit.xcodeproj/project.pbxproj index fab9f142..fc5b240d 100644 --- a/LeadKit/LeadKit.xcodeproj/project.pbxproj +++ b/LeadKit/LeadKit.xcodeproj/project.pbxproj @@ -51,6 +51,13 @@ 78CFEE5B1C5C45E500F50370 /* ViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78CFEE501C5C45E500F50370 /* ViewModelProtocol.swift */; }; 78E59B191C773EE600C6BFE9 /* ObjectsGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78E59B181C773EE600C6BFE9 /* ObjectsGenerator.swift */; }; 78E59B1B1C77470A00C6BFE9 /* ViewsGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78E59B1A1C77470A00C6BFE9 /* ViewsGenerator.swift */; }; + 95B39A781D9BFCC30057BD54 /* UIImageView+LoadingImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95B39A771D9BFCC30057BD54 /* UIImageView+LoadingImage.swift */; }; + 95B39A7A1D9BFD550057BD54 /* UIImage+Loading.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95B39A791D9BFD550057BD54 /* UIImage+Loading.swift */; }; + 95B39A7C1D9C05260057BD54 /* UIImage+Gradients.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95B39A7B1D9C05260057BD54 /* UIImage+Gradients.swift */; }; + 95B39A7E1D9C069B0057BD54 /* UIImage+Text.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95B39A7D1D9C069B0057BD54 /* UIImage+Text.swift */; }; + 95B39A801D9C09440057BD54 /* UIImage+Cropping.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95B39A7F1D9C09440057BD54 /* UIImage+Cropping.swift */; }; + 95B39A841D9C0C3E0057BD54 /* UIImage+Alpha.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95B39A831D9C0C3E0057BD54 /* UIImage+Alpha.swift */; }; + 95B39A861D9D51250057BD54 /* String+Localization.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95B39A851D9D51250057BD54 /* String+Localization.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -111,6 +118,13 @@ 78CFEE501C5C45E500F50370 /* ViewModelProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewModelProtocol.swift; sourceTree = ""; }; 78E59B181C773EE600C6BFE9 /* ObjectsGenerator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ObjectsGenerator.swift; sourceTree = ""; }; 78E59B1A1C77470A00C6BFE9 /* ViewsGenerator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewsGenerator.swift; sourceTree = ""; }; + 95B39A771D9BFCC30057BD54 /* UIImageView+LoadingImage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "UIImageView+LoadingImage.swift"; path = "UIImageView/UIImageView+LoadingImage.swift"; sourceTree = ""; }; + 95B39A791D9BFD550057BD54 /* UIImage+Loading.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIImage+Loading.swift"; sourceTree = ""; }; + 95B39A7B1D9C05260057BD54 /* UIImage+Gradients.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIImage+Gradients.swift"; sourceTree = ""; }; + 95B39A7D1D9C069B0057BD54 /* UIImage+Text.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIImage+Text.swift"; sourceTree = ""; }; + 95B39A7F1D9C09440057BD54 /* UIImage+Cropping.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIImage+Cropping.swift"; sourceTree = ""; }; + 95B39A831D9C0C3E0057BD54 /* UIImage+Alpha.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIImage+Alpha.swift"; sourceTree = ""; }; + 95B39A851D9D51250057BD54 /* String+Localization.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+Localization.swift"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -204,6 +218,7 @@ isa = PBXGroup; children = ( 787783661CA04D4A001CDC9B /* String+SizeCalculation.swift */, + 95B39A851D9D51250057BD54 /* String+Localization.swift */, ); path = String; sourceTree = ""; @@ -289,6 +304,7 @@ 78CFEE441C5C45E500F50370 /* Extensions */ = { isa = PBXGroup; children = ( + 95B39A761D9BFC930057BD54 /* UIImageView */, 78C36F7F1D8021D100E7EBEA /* UIColor */, 78C36F7C1D801E2F00E7EBEA /* Double */, 787783651CA04D14001CDC9B /* String */, @@ -347,6 +363,14 @@ path = UIView; sourceTree = ""; }; + 95B39A761D9BFC930057BD54 /* UIImageView */ = { + isa = PBXGroup; + children = ( + 95B39A771D9BFCC30057BD54 /* UIImageView+LoadingImage.swift */, + ); + name = UIImageView; + sourceTree = ""; + }; C37210711ACDF1042F70C2EB /* UIImage */ = { isa = PBXGroup; children = ( @@ -355,6 +379,11 @@ 78C36F761D80117D00E7EBEA /* UIImage+Transformations.swift */, 78C36F781D8011FA00E7EBEA /* UIImage+Resize.swift */, 78C36F7A1D8015ED00E7EBEA /* UIImage+Creation.swift */, + 95B39A791D9BFD550057BD54 /* UIImage+Loading.swift */, + 95B39A7B1D9C05260057BD54 /* UIImage+Gradients.swift */, + 95B39A7D1D9C069B0057BD54 /* UIImage+Text.swift */, + 95B39A7F1D9C09440057BD54 /* UIImage+Cropping.swift */, + 95B39A831D9C0C3E0057BD54 /* UIImage+Alpha.swift */, ); path = UIImage; sourceTree = ""; @@ -385,13 +414,13 @@ isa = PBXNativeTarget; buildConfigurationList = 78CFEE3E1C5C456B00F50370 /* Build configuration list for PBXNativeTarget "LeadKit" */; buildPhases = ( + 782B1B3D1C7343CD003F8A95 /* Tailor */, + 782B1B3E1C7343E0003F8A95 /* SwiftLint */, 78CFEE251C5C456B00F50370 /* Sources */, 78CFEE261C5C456B00F50370 /* Frameworks */, 78CFEE271C5C456B00F50370 /* Headers */, 78CFEE281C5C456B00F50370 /* Resources */, 78B0FC871C6B314B00358B64 /* Carthage copy-frameworks */, - 782B1B3D1C7343CD003F8A95 /* Tailor */, - 782B1B3E1C7343E0003F8A95 /* SwiftLint */, ); buildRules = ( ); @@ -540,12 +569,16 @@ 787783671CA04D4A001CDC9B /* String+SizeCalculation.swift in Sources */, 78011A641D47ABC500EA16A2 /* UIView+DefaultReuseIdentifier.swift in Sources */, 78CFEE531C5C45E500F50370 /* UITableView+DequeueCustomCell.swift in Sources */, + 95B39A781D9BFCC30057BD54 /* UIImageView+LoadingImage.swift in Sources */, 786D78EC1D53C46E006B2CEA /* AlamofireManager+Extensions.swift in Sources */, + 95B39A841D9C0C3E0057BD54 /* UIImage+Alpha.swift in Sources */, + 95B39A801D9C09440057BD54 /* UIImage+Cropping.swift in Sources */, 78E59B1B1C77470A00C6BFE9 /* ViewsGenerator.swift in Sources */, 78B0FC811C6B2CD500358B64 /* App.swift in Sources */, 78C36F771D80117D00E7EBEA /* UIImage+Transformations.swift in Sources */, 786D78EA1D53C43E006B2CEA /* ApiError.swift in Sources */, 787A071A1D085750009EC97F /* CellsControllerProtocol.swift in Sources */, + 95B39A861D9D51250057BD54 /* String+Localization.swift in Sources */, 78C36F7E1D801E3E00E7EBEA /* Double+Rounding.swift in Sources */, 78CFEE551C5C45E500F50370 /* NibNameProtocol.swift in Sources */, 78CFEE561C5C45E500F50370 /* ReuseIdentifierProtocol.swift in Sources */, @@ -554,9 +587,11 @@ 78E59B191C773EE600C6BFE9 /* ObjectsGenerator.swift in Sources */, 78CFEE5B1C5C45E500F50370 /* ViewModelProtocol.swift in Sources */, 78C36F7B1D8015ED00E7EBEA /* UIImage+Creation.swift in Sources */, + 95B39A7A1D9BFD550057BD54 /* UIImage+Loading.swift in Sources */, 7824CA521CFEE6B700D7B132 /* UIImage+RenderTemplate.swift in Sources */, 78CFEE5A1C5C45E500F50370 /* ViewHeightProtocol.swift in Sources */, 787682FA1CAD40C300532AB3 /* StaticEstimatedViewHeightProtocol.swift in Sources */, + 95B39A7E1D9C069B0057BD54 /* UIImage+Text.swift in Sources */, 78A74EA91C6B373700FE9724 /* UIView+DefaultNibName.swift in Sources */, 786A17A11CB8D71D007F9661 /* UIImage+CapInsetsUtils.swift in Sources */, 78C36F791D8011FA00E7EBEA /* UIImage+Resize.swift in Sources */, @@ -564,6 +599,7 @@ 787783631CA03CA0001CDC9B /* NSIndexPath+ImmutableIndexPath.swift in Sources */, 78CFEE591C5C45E500F50370 /* StoryboardIdentifierProtocol.swift in Sources */, 78011AB31D48B53600EA16A2 /* ApiRequestParameters.swift in Sources */, + 95B39A7C1D9C05260057BD54 /* UIImage+Gradients.swift in Sources */, 78B0FC7D1C6B2BE200358B64 /* LogFormatter.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/LeadKit/LeadKit/Classes/Cache/ObjectsGenerator.swift b/LeadKit/LeadKit/Classes/Cache/ObjectsGenerator.swift index 376f5ef3..8d2f17f4 100644 --- a/LeadKit/LeadKit/Classes/Cache/ObjectsGenerator.swift +++ b/LeadKit/LeadKit/Classes/Cache/ObjectsGenerator.swift @@ -23,10 +23,8 @@ public class ObjectsGenerator { /** initializer function - - parameter poolSize: number of objects to generate - - parameter contructor: objects constructor closure - - - returns: nothing + - parameter poolSize: number of objects to generate + - parameter contructor: objects constructor closure */ init(poolSize: UInt, objectsContructor contructor: ObjectConstructor) { self.poolSize = poolSize diff --git a/LeadKit/LeadKit/Classes/Cache/ViewsGenerator.swift b/LeadKit/LeadKit/Classes/Cache/ViewsGenerator.swift index 352bc3de..5b7bef95 100644 --- a/LeadKit/LeadKit/Classes/Cache/ViewsGenerator.swift +++ b/LeadKit/LeadKit/Classes/Cache/ViewsGenerator.swift @@ -14,9 +14,7 @@ public class ViewsGenerator: ObjectsGenerator { initializer function - parameter poolSize: number of cells to generate - - parameter nibName: view nib name - - - returns: nothing + - parameter nibName: view nib name */ init(poolSize: UInt, nibName: String) { super.init(poolSize: poolSize, objectsContructor: { T.loadFromNib(named: nibName) }) diff --git a/LeadKit/LeadKit/Extensions/Alamofire/AlamofireRequest+Extensions.swift b/LeadKit/LeadKit/Extensions/Alamofire/AlamofireRequest+Extensions.swift index 7ab3ec86..af933a9f 100644 --- a/LeadKit/LeadKit/Extensions/Alamofire/AlamofireRequest+Extensions.swift +++ b/LeadKit/LeadKit/Extensions/Alamofire/AlamofireRequest+Extensions.swift @@ -24,8 +24,8 @@ public extension Alamofire.Request { return .Failure(.Network(error: err)) } - let JSONResponseSerializer = Request.JSONResponseSerializer(options: .AllowFragments) - let result = JSONResponseSerializer.serializeResponse(request, response, data, error) + let jsonResponseSerializer = Request.JSONResponseSerializer(options: .AllowFragments) + let result = jsonResponseSerializer.serializeResponse(request, response, data, error) switch result { case .Success(let value): diff --git a/LeadKit/LeadKit/Extensions/String/String+Localization.swift b/LeadKit/LeadKit/Extensions/String/String+Localization.swift new file mode 100644 index 00000000..98c45e4d --- /dev/null +++ b/LeadKit/LeadKit/Extensions/String/String+Localization.swift @@ -0,0 +1,22 @@ +// +// String+Localization.swift +// LeadKit +// +// Created by Николай Ашанин on 29.09.16. +// Copyright © 2016 Touch Instinct. All rights reserved. +// + +import UIKit + +public extension String { + + /** + method returns localized string with default comment and self name + + - returns: localized string + */ + public func localized() -> String { + return NSLocalizedString(self, comment: "") + } + +} diff --git a/LeadKit/LeadKit/Extensions/UIColor/UIColor+Hex.swift b/LeadKit/LeadKit/Extensions/UIColor/UIColor+Hex.swift index 9c869ade..1ef9f821 100644 --- a/LeadKit/LeadKit/Extensions/UIColor/UIColor+Hex.swift +++ b/LeadKit/LeadKit/Extensions/UIColor/UIColor+Hex.swift @@ -11,13 +11,11 @@ import UIKit public extension UIColor { /** - The shorthand three-digit hexadecimal representation of color. + the shorthand three-digit hexadecimal representation of color. #RGB defines to the color #RRGGBB. - parameter hex3: Three-digit hexadecimal value. - parameter alpha: 0.0 - 1.0. The default is 1.0. - - - returns: new instance with given three-digit hexadecimal value */ public convenience init(hex3: UInt16, alpha: CGFloat = 1) { let red = CGFloat((hex3 & 0xF00) >> 8) / 0xF @@ -27,12 +25,10 @@ public extension UIColor { } /** - The shorthand four-digit hexadecimal representation of color with alpha. + the shorthand four-digit hexadecimal representation of color with alpha. #RGBA defines to the color #RRGGBBAA. - parameter hex4: Four-digit hexadecimal value. - - - returns: new instance with given four-digit hexadecimal value */ public convenience init(hex4: UInt16) { let red = CGFloat((hex4 & 0xF000) >> 12) / 0xF @@ -44,12 +40,10 @@ public extension UIColor { } /** - The six-digit hexadecimal representation of color of the form #RRGGBB. + the six-digit hexadecimal representation of color of the form #RRGGBB. - parameter hex6: Six-digit hexadecimal value. - parameter alpha: alpha: 0.0 - 1.0. The default is 1.0. - - - returns: new instance with given six-digit hexadecimal value */ public convenience init(hex6: UInt32, alpha: CGFloat = 1) { let red = CGFloat((hex6 & 0xFF0000) >> 16) / 0xFF @@ -60,11 +54,9 @@ public extension UIColor { } /** - The six-digit hexadecimal representation of color with alpha of the form #RRGGBBAA. + the six-digit hexadecimal representation of color with alpha of the form #RRGGBBAA. - parameter hex8: Eight-digit hexadecimal value. - - - returns: new instance with given eight-digit hexadecimal value */ public convenience init(hex8: UInt32) { let red = CGFloat((hex8 & 0xFF000000) >> 24) / 0xFF @@ -81,8 +73,6 @@ public extension UIColor { - parameter hexString: hex string with red green and blue values (can have `#` sign) - parameter alpha: alpha component used if not given in hexString - - - returns: new instance with given hex color or nil if hexString is incorrect */ public convenience init?(hexString: String, alpha: CGFloat = 1) { let hexStr = hexString.hasPrefix("#") ? hexString.substringFromIndex(hexString.startIndex.advancedBy(1)) : hexString diff --git a/LeadKit/LeadKit/Extensions/UIImage/UIImage+Alpha.swift b/LeadKit/LeadKit/Extensions/UIImage/UIImage+Alpha.swift new file mode 100644 index 00000000..8f19bff8 --- /dev/null +++ b/LeadKit/LeadKit/Extensions/UIImage/UIImage+Alpha.swift @@ -0,0 +1,123 @@ +// +// UIImage+Alpha.swift +// LeadKit +// +// Created by Николай Ашанин on 28.09.16. +// Copyright © 2016 Touch Instinct. All rights reserved. +// + +import UIKit + +public extension UIImage { + + /** + - returns: true if the image has an alpha layer. + */ + public func hasAlpha() -> Bool { + let alpha = CGImageGetAlphaInfo(CGImage) + switch alpha { + case .First, .Last, .PremultipliedFirst, .PremultipliedLast: + return true + default: + return false + } + } + + /** + - returns: a copy of the given image, adding an alpha channel if it doesn't already have one. + */ + public func applyAlpha() -> UIImage? { + guard !hasAlpha() else { + return self + } + + let imageRef = CGImage + let width = CGImageGetWidth(imageRef) + let height = CGImageGetHeight(imageRef) + let colorSpace = CGImageGetColorSpace(imageRef) + + // The bitsPerComponent and bitmapInfo values are hard-coded to prevent an "unsupported parameter combination" error + let bitmapInfo = CGBitmapInfo(rawValue: CGBitmapInfo.ByteOrderDefault.rawValue | + CGImageAlphaInfo.PremultipliedFirst.rawValue) + let offscreenContext = CGBitmapContextCreate(nil, width, height, 8, 0, colorSpace, bitmapInfo.rawValue) + + // Draw the image into the context and retrieve the new image, which will now have an alpha layer + CGContextDrawImage(offscreenContext, + CGRect(x: 0, y: 0, width: width, height: height), + imageRef) + guard let cgImage = CGBitmapContextCreateImage(offscreenContext) else { + return nil + } + let imageWithAlpha = UIImage(CGImage: cgImage) + return imageWithAlpha + } + + /** + returns a copy of the image with a transparent border of the given size added around its edges. + i.e. For rotating an image without getting jagged edges. + + - parameter padding: The padding amount. + + - returns: A new image. + */ + public func applyPadding(padding: CGFloat) -> UIImage? { + // If the image does not have an alpha layer, add one + guard let image = applyAlpha() else { + return nil + } + let rect = CGRect(x: 0, y: 0, width: size.width + padding * 2, height: size.height + padding * 2) + + // Build a context that's the same dimensions as the new size + let colorSpace = CGImageGetColorSpace(CGImage) + let bitmapInfo = CGImageGetBitmapInfo(CGImage) + let bitsPerComponent = CGImageGetBitsPerComponent(CGImage) + let context = CGBitmapContextCreate(nil, + Int(rect.size.width), + Int(rect.size.height), + bitsPerComponent, 0, colorSpace, + bitmapInfo.rawValue) + + // Draw the image in the center of the context, leaving a gap around the edges + let imageLocation = CGRect(x: padding, y: padding, width: image.size.width, height: image.size.height) + CGContextDrawImage(context, imageLocation, CGImage) + + // Create a mask to make the border transparent, and combine it with the image + let imageWithPadding = imageRefWithPadding(padding, size: rect.size) + guard let cgImage = CGImageCreateWithMask(CGBitmapContextCreateImage(context), imageWithPadding) else { + return nil + } + let transparentImage = UIImage(CGImage: cgImage) + return transparentImage + } + + /** + creates a mask that makes the outer edges transparent and everything else opaque. + The size must include the entire mask (opaque part + transparent border). + + - parameter padding: The padding amount. + - parameter size: The size of the image. + + - returns: A Core Graphics Image Ref + */ + private func imageRefWithPadding(padding: CGFloat, + size: CGSize) -> CGImageRef? { + // Build a context that's the same dimensions as the new size + let colorSpace = CGColorSpaceCreateDeviceGray() + let bitmapInfo = CGBitmapInfo(rawValue: CGBitmapInfo.ByteOrderDefault.rawValue | CGImageAlphaInfo.None.rawValue) + let context = CGBitmapContextCreate(nil, Int(size.width), Int(size.height), 8, 0, colorSpace, bitmapInfo.rawValue) + // Start with a mask that's entirely transparent + CGContextSetFillColorWithColor(context, UIColor.blackColor().CGColor) + CGContextFillRect(context, CGRect(x: 0, y: 0, width: size.width, height: size.height)) + // Make the inner part (within the border) opaque + CGContextSetFillColorWithColor(context, UIColor.whiteColor().CGColor) + let fillRect = CGRect(x: padding, + y: padding, + width: size.width - padding * 2, + height: size.height - padding * 2) + CGContextFillRect(context, fillRect) + // Get an image of the context + let maskImageRef = CGBitmapContextCreateImage(context) + return maskImageRef + } + +} diff --git a/LeadKit/LeadKit/Extensions/UIImage/UIImage+CapInsetsUtils.swift b/LeadKit/LeadKit/Extensions/UIImage/UIImage+CapInsetsUtils.swift index 80d4d84e..6883fb60 100644 --- a/LeadKit/LeadKit/Extensions/UIImage/UIImage+CapInsetsUtils.swift +++ b/LeadKit/LeadKit/Extensions/UIImage/UIImage+CapInsetsUtils.swift @@ -40,7 +40,7 @@ public extension UIImage { } /** - this metho tries to find requested image with specific parameters in cache, and if doesn't exists - create it + this method tries to find requested image with specific parameters in cache, and if doesn't exists - create it - parameter name: name of the image used in UIImage(named:) - parameter size: size of rendered image diff --git a/LeadKit/LeadKit/Extensions/UIImage/UIImage+Creation.swift b/LeadKit/LeadKit/Extensions/UIImage/UIImage+Creation.swift index 7ca919a9..9417d85a 100644 --- a/LeadKit/LeadKit/Extensions/UIImage/UIImage+Creation.swift +++ b/LeadKit/LeadKit/Extensions/UIImage/UIImage+Creation.swift @@ -33,4 +33,25 @@ public extension UIImage { return UIGraphicsGetImageFromCurrentImageContext() } + /** + creates an image from a UIView. + + - parameter fromView: The source view. + + - returns A new image + */ + public convenience init?(fromView view: UIView) { + UIGraphicsBeginImageContextWithOptions(view.bounds.size, false, 0) + + guard let context = UIGraphicsGetCurrentContext() else { + return nil + } + view.layer.renderInContext(context) + guard let cgImage = UIGraphicsGetImageFromCurrentImageContext().CGImage else { + return nil + } + self.init(CGImage: cgImage) + UIGraphicsEndImageContext() + } + } diff --git a/LeadKit/LeadKit/Extensions/UIImage/UIImage+Cropping.swift b/LeadKit/LeadKit/Extensions/UIImage/UIImage+Cropping.swift new file mode 100644 index 00000000..e0d5a149 --- /dev/null +++ b/LeadKit/LeadKit/Extensions/UIImage/UIImage+Cropping.swift @@ -0,0 +1,44 @@ +// +// UIImage+Cropping.swift +// LeadKit +// +// Created by Николай Ашанин on 28.09.16. +// Copyright © 2016 Touch Instinct. All rights reserved. +// + +import UIKit + +public extension UIImage { + + /** + creates a cropped copy of an image. + + - parameter bounds: The bounds of the rectangle inside the image. + + - returns: A new image + */ + public func crop(bounds: CGRect) -> UIImage? { + guard let cgImage = CGImageCreateWithImageInRect(CGImage, bounds) else { + return nil + } + return UIImage(CGImage: cgImage, + scale: 0.0, + orientation: imageOrientation) + } + + /** + crop image to square + + - returns: cropped image + */ + public func cropToSquare() -> UIImage? { + let scaledSize = CGSize(width: size.width * scale, height: size.height * scale) + let shortest = min(scaledSize.width, scaledSize.height) + let left: CGFloat = scaledSize.width > shortest ? (scaledSize.width-shortest)/2 : 0 + let top: CGFloat = scaledSize.height > shortest ? (scaledSize.height-shortest)/2 : 0 + let rect = CGRect(x: 0, y: 0, width: scaledSize.width, height: scaledSize.height) + let insetRect = CGRectInset(rect, left, top) + return crop(insetRect) + } + +} diff --git a/LeadKit/LeadKit/Extensions/UIImage/UIImage+Gradients.swift b/LeadKit/LeadKit/Extensions/UIImage/UIImage+Gradients.swift new file mode 100644 index 00000000..78c12d0e --- /dev/null +++ b/LeadKit/LeadKit/Extensions/UIImage/UIImage+Gradients.swift @@ -0,0 +1,123 @@ +// +// UIImage+Gradients.swift +// LeadKit +// +// Created by Николай Ашанин on 28.09.16. +// Copyright © 2016 Touch Instinct. All rights reserved. +// + +import UIKit + +public extension UIImage { + + /** + creates a gradient color image. + + - parameter gradientColors: An array of colors to use for the gradient. + - parameter size: Image size (defaults: 10x10) + */ + public convenience init?(gradientColors: [UIColor], + size: CGSize = CGSize(width: 10, height: 10)) { + UIGraphicsBeginImageContextWithOptions(size, false, 0) + let context = UIGraphicsGetCurrentContext() + let colorSpace = CGColorSpaceCreateDeviceRGB() + let colors = gradientColors.map {(color: UIColor) -> AnyObject? in return color.CGColor as AnyObject? } as NSArray + let gradient = CGGradientCreateWithColors(colorSpace, colors, nil) + CGContextDrawLinearGradient(context, + gradient, + CGPoint(x: 0, y: 0), + CGPoint(x: 0, y: size.height), + CGGradientDrawingOptions(rawValue: 0)) + guard let cgImage = UIGraphicsGetImageFromCurrentImageContext().CGImage else { + return nil + } + self.init(CGImage: cgImage) + UIGraphicsEndImageContext() + } + + /** + applies gradient color overlay to an image. + + - parameter gradientColors: An array of colors to use for the gradient. + - parameter blendMode: The blending type to use. + + - returns: A new image + */ + public func applyGradientColors(gradientColors: [UIColor], + blendMode: CGBlendMode = .Normal) -> UIImage { + UIGraphicsBeginImageContextWithOptions(size, false, scale) + let context = UIGraphicsGetCurrentContext() + CGContextTranslateCTM(context, 0, size.height) + CGContextScaleCTM(context, 1.0, -1.0) + CGContextSetBlendMode(context, blendMode) + let rect = CGRect(x: 0, y: 0, width: size.width, height: size.height) + CGContextDrawImage(context, rect, CGImage) + // Create gradient + let colorSpace = CGColorSpaceCreateDeviceRGB() + let colors = gradientColors.map {(color: UIColor) -> AnyObject? in return color.CGColor as AnyObject? } as NSArray + let gradient = CGGradientCreateWithColors(colorSpace, colors, nil) + // Apply gradient + CGContextClipToMask(context, rect, CGImage) + CGContextDrawLinearGradient(context, + gradient, + CGPoint(x: 0, y: 0), + CGPoint(x: 0, y: size.height), + CGGradientDrawingOptions(rawValue: 0)) + let image = UIGraphicsGetImageFromCurrentImageContext() + UIGraphicsEndImageContext() + return image + } + + /** + creates a radial gradient. + + - parameter startColor: The start color + - parameter endColor: The end color + - parameter radialGradientCenter: The gradient center (default:0.5,0.5). + - parameter radius: Radius size (default: 0.5) + - parameter size: Image size (default: 100x100) + */ + public convenience init?(startColor: UIColor, + endColor: UIColor, + radialGradientCenter: CGPoint = CGPoint(x: 0.5, y: 0.5), + radius: CGFloat = 0.5, + size: CGSize = CGSize(width: 100, height: 100)) { + UIGraphicsBeginImageContextWithOptions(size, true, 0) + + let numLocations: Int = 2 + let locations: [CGFloat] = [0.0, 1.0] + + let startComponents = CGColorGetComponents(startColor.CGColor) + let endComponents = CGColorGetComponents(endColor.CGColor) + + let components: [CGFloat] = [startComponents[0], + startComponents[1], + startComponents[2], + startComponents[3], + endComponents[0], + endComponents[1], + endComponents[2], + endComponents[3]] as [CGFloat] + + let colorSpace = CGColorSpaceCreateDeviceRGB() + let gradient = CGGradientCreateWithColorComponents(colorSpace, components, locations, numLocations) + + // Normalize the 0-1 ranged inputs to the width of the image + let aCenter = CGPoint(x: radialGradientCenter.x * size.width, + y: radialGradientCenter.y * size.height) + let aRadius = (min(size.width, size.height)) * (radius) + + // Draw it + CGContextDrawRadialGradient(UIGraphicsGetCurrentContext(), + gradient, aCenter, 0, + aCenter, aRadius, + CGGradientDrawingOptions.DrawsAfterEndLocation) + guard let cgImage = UIGraphicsGetImageFromCurrentImageContext().CGImage else { + return nil + } + self.init(CGImage: cgImage) + // Clean up + UIGraphicsEndImageContext() + } + +} diff --git a/LeadKit/LeadKit/Extensions/UIImage/UIImage+Loading.swift b/LeadKit/LeadKit/Extensions/UIImage/UIImage+Loading.swift new file mode 100644 index 00000000..31751cf0 --- /dev/null +++ b/LeadKit/LeadKit/Extensions/UIImage/UIImage+Loading.swift @@ -0,0 +1,74 @@ +// +// UIImage+Loading.swift +// LeadKit +// +// Created by Николай Ашанин on 28.09.16. +// Copyright © 2016 Touch Instinct. All rights reserved. +// + +import UIKit +import QuartzCore +import CoreGraphics +import Accelerate + +public enum UIImageContentMode { + case ScaleToFill, ScaleAspectFit, ScaleAspectFill +} + +public extension UIImage { + + /** + a singleton shared NSURL cache used for images from URL + */ + static var sharedCache: NSCache = { + return NSCache() + } + + // MARK: Image From URL + + /** + creates a new image from a URL with optional caching. + if using cache, the cached image is returned. + otherwise, a place holder is used until the image from web is returned by the fetchComplete. + + - parameter url: The image URL. + - parameter placeholder: The placeholder image. + - parameter cacheImage: Weather or not we should cache the NSURL response (default: true) + - parameter fetchComplete: Returns the image from the web the first time is fetched. + + - returns: A new image + */ + public class func imageFromURL(url: String, + placeholder: UIImage, + cacheImage: Bool = true, + fetchComplete: (image: UIImage?) -> ()) -> UIImage? { + // From Cache + if cacheImage { + if let image = UIImage.sharedCache().objectForKey(url) as? UIImage { + fetchComplete(image: nil) + return image + } + } + // Fetch Image + let session = NSURLSession(configuration: NSURLSessionConfiguration.defaultSessionConfiguration()) + if let nsURL = NSURL(string: url) { + session.dataTaskWithURL(nsURL, completionHandler: { (data, response, error) -> Void in + if error != nil { + dispatch_async(dispatch_get_main_queue()) { + fetchComplete(image: nil) + } + } else if let data = data, image = UIImage(data: data) { + if cacheImage { + UIImage.sharedCache().setObject(image, forKey: url) + } + dispatch_async(dispatch_get_main_queue()) { + fetchComplete(image: image) + } + } + session.finishTasksAndInvalidate() + }).resume() + } + return placeholder + } + +} diff --git a/LeadKit/LeadKit/Extensions/UIImage/UIImage+Resize.swift b/LeadKit/LeadKit/Extensions/UIImage/UIImage+Resize.swift index 4acae7da..46fedd49 100644 --- a/LeadKit/LeadKit/Extensions/UIImage/UIImage+Resize.swift +++ b/LeadKit/LeadKit/Extensions/UIImage/UIImage+Resize.swift @@ -28,4 +28,53 @@ public extension UIImage { return UIGraphicsGetImageFromCurrentImageContext() } + /** + creates a resized copy of an image. + + - parameter size: the new size of the image. + - parameter contentMode: the way to handle the content in the new size. + + - returns: a new image + */ + public func resize(size: CGSize, + contentMode: UIImageContentMode = .ScaleToFill) -> UIImage? { + let horizontalRatio = size.width / size.width + let verticalRatio = size.height / size.height + var ratio: CGFloat = 1 + + switch contentMode { + case .ScaleToFill: + ratio = 1 + case .ScaleAspectFill: + ratio = max(horizontalRatio, verticalRatio) + case .ScaleAspectFit: + ratio = min(horizontalRatio, verticalRatio) + } + + let rect = CGRect(x: 0, y: 0, width: size.width * ratio, height: size.height * ratio) + + let colorSpace = CGColorSpaceCreateDeviceRGB() + let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.PremultipliedLast.rawValue) + let context = CGBitmapContextCreate(nil, + Int(rect.size.width), + Int(rect.size.height), 8, 0, colorSpace, + bitmapInfo.rawValue) + let transform = CGAffineTransformIdentity + CGContextConcatCTM(context, transform) + // Set the quality level to use when rescaling + guard let interpolationQuality = CGInterpolationQuality(rawValue: 3) else { + return nil + } + CGContextSetInterpolationQuality(context, interpolationQuality) + CGContextDrawImage(context, rect, CGImage) + + guard let newContext = context else { + return nil + } + let newImage = UIImage(CGImage: CGBitmapContextCreateImage(newContext), + scale: scale, + orientation: imageOrientation) + return newImage + } + } diff --git a/LeadKit/LeadKit/Extensions/UIImage/UIImage+Text.swift b/LeadKit/LeadKit/Extensions/UIImage/UIImage+Text.swift new file mode 100644 index 00000000..79dfe2b2 --- /dev/null +++ b/LeadKit/LeadKit/Extensions/UIImage/UIImage+Text.swift @@ -0,0 +1,45 @@ +// +// UIImage+Text.swift +// LeadKit +// +// Created by Николай Ашанин on 28.09.16. +// Copyright © 2016 Touch Instinct. All rights reserved. +// + +import UIKit + +public extension UIImage { + + /** + creates a text label image. + + - parameter text: The text to use in the label. + - parameter font: The font (default: System font of size 18) + - parameter color: The text color (default: White) + - parameter backgroundColor: The background color (default:Gray). + - parameter size: Image size (default: 10x10) + - parameter offset: Center offset (default: 0x0) + */ + public convenience init?(text: String, + font: UIFont = UIFont.systemFontOfSize(18), + color: UIColor = UIColor.whiteColor(), + backgroundColor: UIColor = UIColor.grayColor(), + size: CGSize = CGSize(width: 10, height: 10), + offset: CGPoint = CGPoint(x: 0, y: 0)) { + let label = UILabel(frame: CGRect(x: 0, y: 0, width: size.width, height: size.height)) + label.font = font + label.text = text + label.textColor = color + label.textAlignment = .Center + label.backgroundColor = backgroundColor + let image = UIImage(fromView: label) + UIGraphicsBeginImageContextWithOptions(size, false, 0) + image?.drawInRect(CGRect(x: 0, y: 0, width: size.width, height: size.height)) + guard let cgImage = UIGraphicsGetImageFromCurrentImageContext().CGImage else { + return nil + } + self.init(CGImage: cgImage) + UIGraphicsEndImageContext() + } + +} diff --git a/LeadKit/LeadKit/Extensions/UIImage/UIImage+Transformations.swift b/LeadKit/LeadKit/Extensions/UIImage/UIImage+Transformations.swift index bad69574..4fa8864b 100644 --- a/LeadKit/LeadKit/Extensions/UIImage/UIImage+Transformations.swift +++ b/LeadKit/LeadKit/Extensions/UIImage/UIImage+Transformations.swift @@ -32,4 +32,123 @@ public extension UIImage { return UIGraphicsGetImageFromCurrentImageContext() } + /** + creates a new image with rounded corners. + + - parameter cornerRadius: The corner radius. + + - returns: A new image + */ + public func roundCorners(cornerRadius: CGFloat) -> UIImage? { + guard let imageWithAlpha = applyAlpha() else { + return nil + } + UIGraphicsBeginImageContextWithOptions(size, false, 0) + let width = CGImageGetWidth(imageWithAlpha?.CGImage) + let height = CGImageGetHeight(imageWithAlpha?.CGImage) + let bits = CGImageGetBitsPerComponent(imageWithAlpha?.CGImage) + let colorSpace = CGImageGetColorSpace(imageWithAlpha?.CGImage) + let bitmapInfo = CGImageGetBitmapInfo(imageWithAlpha?.CGImage) + let context = CGBitmapContextCreate(nil, width, height, bits, 0, colorSpace, bitmapInfo.rawValue) + let rect = CGRect(x: 0, y: 0, width: CGFloat(width) * scale, height: CGFloat(height) * scale) + CGContextBeginPath(context) + if cornerRadius == 0 { + CGContextAddRect(context, rect) + } else { + CGContextSaveGState(context) + CGContextTranslateCTM(context, rect.minX, rect.minY) + CGContextScaleCTM(context, cornerRadius, cornerRadius) + let roundedWidth = rect.size.width / cornerRadius + let roundedHeight = rect.size.height / cornerRadius + CGContextMoveToPoint(context, roundedWidth, roundedHeight/2) + CGContextAddArcToPoint(context, roundedWidth, roundedHeight, roundedWidth/2, roundedHeight, 1) + CGContextAddArcToPoint(context, 0, roundedHeight, 0, roundedHeight/2, 1) + CGContextAddArcToPoint(context, 0, 0, roundedWidth/2, 0, 1) + CGContextAddArcToPoint(context, roundedWidth, 0, roundedWidth, roundedHeight/2, 1) + CGContextRestoreGState(context) + } + CGContextClosePath(context) + CGContextClip(context) + CGContextDrawImage(context, rect, imageWithAlpha?.CGImage) + guard let bitmapImage = CGBitmapContextCreateImage(context) else { + return nil + } + let image = UIImage(CGImage: bitmapImage, + scale: scale, + orientation: .Up) + UIGraphicsEndImageContext() + return image + } + + /** + creates a new image with rounded corners and border. + + - parameter cornerRadius: The corner radius. + - parameter border: The size of the border. + - parameter color: The color of the border. + + - returns: A new image + */ + public func roundCorners(cornerRadius: CGFloat, + border: CGFloat, + color: UIColor) -> UIImage? { + return roundCorners(cornerRadius)?.applyBorder(border, color: color) + } + + /** + creates a new circle image. + + - returns: A new image + */ + public func roundCornersToCircle() -> UIImage? { + let shortest = min(size.width, size.height) + return cropToSquare()?.roundCorners(shortest/2) + } + + /** + creates a new circle image with a border. + + - parameter border: CGFloat The size of the border. + - parameter color: UIColor The color of the border. + + - returns: UIImage? + */ + public func roundCornersToCircle(border border: CGFloat, color: UIColor) -> UIImage? { + let shortest = min(size.width, size.height) + return cropToSquare()?.roundCorners(shortest/2, border: border, color: color) + } + + /** + creates a new image with a border. + + - parameter border: The size of the border. + - parameter color: The color of the border. + + - returns: A new image + */ + public func applyBorder(border: CGFloat, + color: UIColor) -> UIImage? { + UIGraphicsBeginImageContextWithOptions(size, false, 0) + let width = CGImageGetWidth(CGImage) + let height = CGImageGetHeight(CGImage) + let bits = CGImageGetBitsPerComponent(CGImage) + let colorSpace = CGImageGetColorSpace(CGImage) + let bitmapInfo = CGImageGetBitmapInfo(CGImage) + let context = CGBitmapContextCreate(nil, width, height, bits, 0, colorSpace, bitmapInfo.rawValue) + var red: CGFloat = 0, green: CGFloat = 0, blue: CGFloat = 0, alpha: CGFloat = 0 + color.getRed(&red, green: &green, blue: &blue, alpha: &alpha) + CGContextSetRGBStrokeColor(context, red, green, blue, alpha) + CGContextSetLineWidth(context, border) + let rect = CGRect(x: 0, y: 0, width: size.width*scale, height: size.height*scale) + let inset = CGRectInset(rect, border*scale, border*scale) + CGContextStrokeEllipseInRect(context, inset) + CGContextDrawImage(context, inset, CGImage) + guard let bitmapImage = CGBitmapContextCreateImage(context) else { + return nil + } + let image = UIImage(CGImage: bitmapImage) + UIGraphicsEndImageContext() + return image + } + } diff --git a/LeadKit/LeadKit/Extensions/UIImageView/UIImageView+LoadingImage.swift b/LeadKit/LeadKit/Extensions/UIImageView/UIImageView+LoadingImage.swift new file mode 100644 index 00000000..4e1db836 --- /dev/null +++ b/LeadKit/LeadKit/Extensions/UIImageView/UIImageView+LoadingImage.swift @@ -0,0 +1,52 @@ +// +// UIImageView+LoadingImage.swift +// LeadKit +// +// Created by Николай Ашанин on 28.09.16. +// Copyright © 2016 Touch Instinct. All rights reserved. +// + +import UIKit +import QuartzCore + +public extension UIImageView { + + /** + loads an image from a URL. If cached, the cached image is returned. + otherwise, a place holder is used until the image from web is returned by the closure. + + - parameter url: The image URL. + - parameter placeholder: The placeholder image. + - parameter fadeIn: Weather the mage should fade in. + - parameter shouldCacheImage: Should be image cached. + - parameter closure: Returns the image from the web the first time is fetched. + + - returns: A new image + */ + public func imageFromURL(url: String, + placeholder: UIImage, + fadeIn: Bool = true, + shouldCacheImage: Bool = true, + closure: ((image: UIImage?) -> ())? = nil) { + + image = UIImage.imageFromURL(url, + placeholder: placeholder, + shouldCacheImage: shouldCacheImage) { [weak self] + (uploadedImage: UIImage?) in + + guard let image = uploadedImage else { + return nil + } + self?.image = image + if fadeIn { + let transition = CATransition() + transition.duration = 0.5 + transition.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut) + transition.type = kCATransitionFade + layer.addAnimation(transition, forKey: nil) + } + closure?(image: image) + } + } + +}