new API for drawing

This commit is contained in:
Ivan Smolin 2017-04-27 09:37:23 +03:00
parent b74a516720
commit 020ea512f4
16 changed files with 862 additions and 379 deletions

View File

@ -10,6 +10,7 @@
6727419D1E65B99E0075836A /* MappableUserDefaultsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6727419C1E65B99E0075836A /* MappableUserDefaultsTests.swift */; };
672741A01E65C1E00075836A /* Post.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6727419F1E65C1E00075836A /* Post.swift */; };
674743941E929A5A00B47671 /* PaginationViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 674743931E929A5A00B47671 /* PaginationViewModelTests.swift */; };
674E7E651EB0F2E300D13340 /* UIImage+SupportExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 674E7E641EB0F2E300D13340 /* UIImage+SupportExtensions.swift */; };
675D24B21E9234BB00E92D1F /* PaginationViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 675D24B11E9234BB00E92D1F /* PaginationViewModel.swift */; };
675FB4251EA7797C0075BF3D /* Mutex.swift in Sources */ = {isa = PBXBuildFile; fileRef = 675FB4241EA7797C0075BF3D /* Mutex.swift */; };
676D177E1EAE0661002E19F9 /* ResizeContentMode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 676D177D1EAE0661002E19F9 /* ResizeContentMode.swift */; };
@ -18,6 +19,15 @@
678A202A1E93C1A900787562 /* PaginationTableViewWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 678A20291E93C1A900787562 /* PaginationTableViewWrapper.swift */; };
679DE4901E9588B6006F25FE /* SupportProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 679DE48F1E9588B6006F25FE /* SupportProtocol.swift */; };
679DE4941E9613ED006F25FE /* UIScrollView+Support.swift in Sources */ = {isa = PBXBuildFile; fileRef = 679DE4931E9613ED006F25FE /* UIScrollView+Support.swift */; };
67A7B1911EAF5F4900E5BC59 /* ImageDrawingOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67A7B1901EAF5F4900E5BC59 /* ImageDrawingOperation.swift */; };
67A7B1931EAF5F6A00E5BC59 /* TemplateDrawingOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67A7B1921EAF5F6A00E5BC59 /* TemplateDrawingOperation.swift */; };
67A7B1951EAF5F9B00E5BC59 /* CGSize+CGContextSize.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67A7B1941EAF5F9B00E5BC59 /* CGSize+CGContextSize.swift */; };
67A7B1971EAF5FF600E5BC59 /* DrawingOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67A7B1961EAF5FF600E5BC59 /* DrawingOperation.swift */; };
67A7B1991EAF602900E5BC59 /* RoundDrawingOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67A7B1981EAF602900E5BC59 /* RoundDrawingOperation.swift */; };
67A7B19B1EAF60B100E5BC59 /* BorderDrawingOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67A7B19A1EAF60B100E5BC59 /* BorderDrawingOperation.swift */; };
67A7B19F1EAF646400E5BC59 /* PaddingDrawingOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67A7B19E1EAF646400E5BC59 /* PaddingDrawingOperation.swift */; };
67A7B1A11EAF67AE00E5BC59 /* SolidFillDrawingOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67A7B1A01EAF67AE00E5BC59 /* SolidFillDrawingOperation.swift */; };
67A7B1A31EAF6B4600E5BC59 /* CALayerDrawingOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67A7B1A21EAF6B4600E5BC59 /* CALayerDrawingOperation.swift */; };
67B3057B1E8A8727008169CA /* TestView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 67B3057A1E8A8727008169CA /* TestView.xib */; };
67B3057D1E8A8735008169CA /* TestView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67B3057C1E8A8735008169CA /* TestView.swift */; };
67B3057F1E8A8804008169CA /* LoadFromNibTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67B3057E1E8A8804008169CA /* LoadFromNibTests.swift */; };
@ -61,10 +71,6 @@
78A0FCC81DC366A10070B5E1 /* StoryboardProtocol+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78A0FCC61DC366A10070B5E1 /* StoryboardProtocol+Extensions.swift */; };
78A74EA91C6B373700FE9724 /* UIView+DefaultXibName.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78A74EA81C6B373700FE9724 /* UIView+DefaultXibName.swift */; };
78B036411DA4D7060021D5CC /* UIImage+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78B036401DA4D7060021D5CC /* UIImage+Extensions.swift */; };
78B036431DA4FEC90021D5CC /* CGImage+Transform.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78B036421DA4FEC90021D5CC /* CGImage+Transform.swift */; };
78B036451DA561D00021D5CC /* CGImage+Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78B036441DA561D00021D5CC /* CGImage+Utils.swift */; };
78B036471DA5624D0021D5CC /* CGImage+Creation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78B036461DA5624D0021D5CC /* CGImage+Creation.swift */; };
78B036491DA562C30021D5CC /* CGImage+Template.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78B036481DA562C30021D5CC /* CGImage+Template.swift */; };
78B0364B1DA61EDE0021D5CC /* CGImage+Crop.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78B0364A1DA61EDE0021D5CC /* CGImage+Crop.swift */; };
78B0FC7D1C6B2BE200358B64 /* LogFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78B0FC7C1C6B2BE200358B64 /* LogFormatter.swift */; };
78B0FC7F1C6B2C4D00358B64 /* Log.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78B0FC7E1C6B2C4D00358B64 /* Log.swift */; };
@ -115,6 +121,7 @@
6727419C1E65B99E0075836A /* MappableUserDefaultsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MappableUserDefaultsTests.swift; sourceTree = "<group>"; };
6727419F1E65C1E00075836A /* Post.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Post.swift; sourceTree = "<group>"; };
674743931E929A5A00B47671 /* PaginationViewModelTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PaginationViewModelTests.swift; sourceTree = "<group>"; };
674E7E641EB0F2E300D13340 /* UIImage+SupportExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIImage+SupportExtensions.swift"; sourceTree = "<group>"; };
675D24B11E9234BB00E92D1F /* PaginationViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PaginationViewModel.swift; sourceTree = "<group>"; };
675FB4241EA7797C0075BF3D /* Mutex.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Mutex.swift; sourceTree = "<group>"; };
676D177D1EAE0661002E19F9 /* ResizeContentMode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ResizeContentMode.swift; sourceTree = "<group>"; };
@ -123,6 +130,15 @@
678A20291E93C1A900787562 /* PaginationTableViewWrapper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PaginationTableViewWrapper.swift; sourceTree = "<group>"; };
679DE48F1E9588B6006F25FE /* SupportProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SupportProtocol.swift; sourceTree = "<group>"; };
679DE4931E9613ED006F25FE /* UIScrollView+Support.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIScrollView+Support.swift"; sourceTree = "<group>"; };
67A7B1901EAF5F4900E5BC59 /* ImageDrawingOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageDrawingOperation.swift; sourceTree = "<group>"; };
67A7B1921EAF5F6A00E5BC59 /* TemplateDrawingOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TemplateDrawingOperation.swift; sourceTree = "<group>"; };
67A7B1941EAF5F9B00E5BC59 /* CGSize+CGContextSize.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CGSize+CGContextSize.swift"; sourceTree = "<group>"; };
67A7B1961EAF5FF600E5BC59 /* DrawingOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DrawingOperation.swift; sourceTree = "<group>"; };
67A7B1981EAF602900E5BC59 /* RoundDrawingOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RoundDrawingOperation.swift; sourceTree = "<group>"; };
67A7B19A1EAF60B100E5BC59 /* BorderDrawingOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BorderDrawingOperation.swift; sourceTree = "<group>"; };
67A7B19E1EAF646400E5BC59 /* PaddingDrawingOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PaddingDrawingOperation.swift; sourceTree = "<group>"; };
67A7B1A01EAF67AE00E5BC59 /* SolidFillDrawingOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SolidFillDrawingOperation.swift; sourceTree = "<group>"; };
67A7B1A21EAF6B4600E5BC59 /* CALayerDrawingOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CALayerDrawingOperation.swift; sourceTree = "<group>"; };
67B3057A1E8A8727008169CA /* TestView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = TestView.xib; sourceTree = "<group>"; };
67B3057C1E8A8735008169CA /* TestView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestView.swift; sourceTree = "<group>"; };
67B3057E1E8A8804008169CA /* LoadFromNibTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoadFromNibTests.swift; sourceTree = "<group>"; };
@ -166,10 +182,6 @@
78A0FCC61DC366A10070B5E1 /* StoryboardProtocol+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "StoryboardProtocol+Extensions.swift"; sourceTree = "<group>"; };
78A74EA81C6B373700FE9724 /* UIView+DefaultXibName.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "UIView+DefaultXibName.swift"; path = "LeadKit/Extensions/UIView/UIView+DefaultXibName.swift"; sourceTree = SOURCE_ROOT; };
78B036401DA4D7060021D5CC /* UIImage+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIImage+Extensions.swift"; sourceTree = "<group>"; };
78B036421DA4FEC90021D5CC /* CGImage+Transform.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CGImage+Transform.swift"; sourceTree = "<group>"; };
78B036441DA561D00021D5CC /* CGImage+Utils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CGImage+Utils.swift"; sourceTree = "<group>"; };
78B036461DA5624D0021D5CC /* CGImage+Creation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CGImage+Creation.swift"; sourceTree = "<group>"; };
78B036481DA562C30021D5CC /* CGImage+Template.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CGImage+Template.swift"; sourceTree = "<group>"; };
78B0364A1DA61EDE0021D5CC /* CGImage+Crop.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CGImage+Crop.swift"; sourceTree = "<group>"; };
78B0FC7C1C6B2BE200358B64 /* LogFormatter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LogFormatter.swift; sourceTree = "<group>"; };
78B0FC7E1C6B2C4D00358B64 /* Log.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Log.swift; sourceTree = "<group>"; };
@ -260,6 +272,7 @@
isa = PBXGroup;
children = (
676D17801EAE137B002E19F9 /* CGSize+Resize.swift */,
67A7B1941EAF5F9B00E5BC59 /* CGSize+CGContextSize.swift */,
);
path = CGSize;
sourceTree = "<group>";
@ -280,6 +293,20 @@
path = Support;
sourceTree = "<group>";
};
67A7B18F1EAF5F2200E5BC59 /* DrawingOperations */ = {
isa = PBXGroup;
children = (
67A7B1901EAF5F4900E5BC59 /* ImageDrawingOperation.swift */,
67A7B1921EAF5F6A00E5BC59 /* TemplateDrawingOperation.swift */,
67A7B1981EAF602900E5BC59 /* RoundDrawingOperation.swift */,
67A7B19A1EAF60B100E5BC59 /* BorderDrawingOperation.swift */,
67A7B19E1EAF646400E5BC59 /* PaddingDrawingOperation.swift */,
67A7B1A01EAF67AE00E5BC59 /* SolidFillDrawingOperation.swift */,
67A7B1A21EAF6B4600E5BC59 /* CALayerDrawingOperation.swift */,
);
path = DrawingOperations;
sourceTree = "<group>";
};
67B305791E8A8727008169CA /* Views */ = {
isa = PBXGroup;
children = (
@ -344,6 +371,7 @@
children = (
67DC650A1E979BFD002F2FFF /* Views */,
78011AB11D48B53600EA16A2 /* Api */,
67A7B18F1EAF5F2200E5BC59 /* DrawingOperations */,
);
path = Structures;
sourceTree = "<group>";
@ -360,10 +388,6 @@
isa = PBXGroup;
children = (
780D23421DA412470084620D /* CGImage+Alpha.swift */,
78B036421DA4FEC90021D5CC /* CGImage+Transform.swift */,
78B036441DA561D00021D5CC /* CGImage+Utils.swift */,
78B036461DA5624D0021D5CC /* CGImage+Creation.swift */,
78B036481DA562C30021D5CC /* CGImage+Template.swift */,
78B0364A1DA61EDE0021D5CC /* CGImage+Crop.swift */,
);
path = CGImage;
@ -616,6 +640,7 @@
67B856E21E923BE600F54304 /* ResettableType.swift */,
679DE48F1E9588B6006F25FE /* SupportProtocol.swift */,
67DC65031E979B34002F2FFF /* LoadingIndicatorProtocol.swift */,
67A7B1961EAF5FF600E5BC59 /* DrawingOperation.swift */,
);
path = Protocols;
sourceTree = "<group>";
@ -655,6 +680,7 @@
isa = PBXGroup;
children = (
78B036401DA4D7060021D5CC /* UIImage+Extensions.swift */,
674E7E641EB0F2E300D13340 /* UIImage+SupportExtensions.swift */,
);
path = UIImage;
sourceTree = "<group>";
@ -903,7 +929,7 @@
};
782B1B3D1C7343CD003F8A95 /* Tailor */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
buildActionMask = 8;
files = (
);
inputPaths = (
@ -911,13 +937,13 @@
name = Tailor;
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
runOnlyForDeploymentPostprocessing = 1;
shellPath = /bin/sh;
shellScript = "if hash tailor 2>/dev/null; then\n tailor --except=trailing-whitespace,forced-type-cast\nelse\n echo \"warning: Please install Tailor from https://tailor.sh\"\nfi";
};
782B1B3E1C7343E0003F8A95 /* SwiftLint */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
buildActionMask = 8;
files = (
);
inputPaths = (
@ -925,7 +951,7 @@
name = SwiftLint;
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
runOnlyForDeploymentPostprocessing = 1;
shellPath = /bin/sh;
shellScript = "if which swiftlint >/dev/null; then\n swiftlint\nelse\n echo \"SwiftLint does not exist, download from https://github.com/realm/SwiftLint\"\nfi";
};
@ -951,6 +977,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
67A7B1971EAF5FF600E5BC59 /* DrawingOperation.swift in Sources */,
787D874A1E10E1A400D6015C /* ImmutableMappable+ObservableMappable.swift in Sources */,
780F56CA1E0D76B8004530B6 /* Sequence+ConcurrentMap.swift in Sources */,
7837F60F1CBCF5C0000D74C1 /* EstimatedViewHeightProtocol.swift in Sources */,
@ -964,11 +991,11 @@
78753E2C1DE58BF9006BC0FB /* StaticCursor.swift in Sources */,
78D4B54A1DA64EAB005B0764 /* Any+TypeName.swift in Sources */,
78CFEE571C5C45E500F50370 /* XibNameProtocol.swift in Sources */,
67A7B1A11EAF67AE00E5BC59 /* SolidFillDrawingOperation.swift in Sources */,
67DC650C1E979C0A002F2FFF /* AnyLoadingIndicator.swift in Sources */,
788EC15A1CF64528009CFB6B /* UIStoryboard+InstantiateViewController.swift in Sources */,
787783671CA04D4A001CDC9B /* String+SizeCalculation.swift in Sources */,
7873D1511E112B0D001816EB /* Any+Cast.swift in Sources */,
78B036431DA4FEC90021D5CC /* CGImage+Transform.swift in Sources */,
78011A641D47ABC500EA16A2 /* UIView+DefaultReuseIdentifier.swift in Sources */,
678A202A1E93C1A900787562 /* PaginationTableViewWrapper.swift in Sources */,
679DE4901E9588B6006F25FE /* SupportProtocol.swift in Sources */,
@ -976,13 +1003,15 @@
CAA707D51E2E614E0022D732 /* ModuleConfigurator.swift in Sources */,
675D24B21E9234BB00E92D1F /* PaginationViewModel.swift in Sources */,
78B0FC811C6B2CD500358B64 /* App.swift in Sources */,
78B036491DA562C30021D5CC /* CGImage+Template.swift in Sources */,
676D177E1EAE0661002E19F9 /* ResizeContentMode.swift in Sources */,
CAE698C61E96775F000394B0 /* String+Extensions.swift in Sources */,
7873D14F1E1127BC001816EB /* LeadKitError.swift in Sources */,
674E7E651EB0F2E300D13340 /* UIImage+SupportExtensions.swift in Sources */,
67A7B1991EAF602900E5BC59 /* RoundDrawingOperation.swift in Sources */,
78753E301DE594B4006BC0FB /* MapCursor.swift in Sources */,
679DE4941E9613ED006F25FE /* UIScrollView+Support.swift in Sources */,
780D23461DA416F80084620D /* CGContext+Initializers.swift in Sources */,
67A7B19B1EAF60B100E5BC59 /* BorderDrawingOperation.swift in Sources */,
95B39A861D9D51250057BD54 /* String+Localization.swift in Sources */,
78C36F7E1D801E3E00E7EBEA /* Double+Rounding.swift in Sources */,
67DC65061E979B70002F2FFF /* UIView+LoadingIndicator.swift in Sources */,
@ -990,8 +1019,11 @@
67B305841E8A92E8008169CA /* XibView.swift in Sources */,
78C54AFD1E432EEF0051EFBA /* UIViewController+TopVisibleViewController.swift in Sources */,
67788F9F1E69661800484DEE /* CGFloat+Pixels.swift in Sources */,
67A7B1951EAF5F9B00E5BC59 /* CGSize+CGContextSize.swift in Sources */,
783AF06B1E41CE6C00EC5ADE /* Observable+ToastErrorLogging.swift in Sources */,
67A7B1A31EAF6B4600E5BC59 /* CALayerDrawingOperation.swift in Sources */,
78CFEE561C5C45E500F50370 /* ReuseIdentifierProtocol.swift in Sources */,
67A7B19F1EAF646400E5BC59 /* PaddingDrawingOperation.swift in Sources */,
78A0FCC81DC366A10070B5E1 /* StoryboardProtocol+Extensions.swift in Sources */,
78B036411DA4D7060021D5CC /* UIImage+Extensions.swift in Sources */,
78A0FCC71DC366A10070B5E1 /* StoryboardProtocol+DefaultBundle.swift in Sources */,
@ -1000,6 +1032,8 @@
78C36F811D8021DD00E7EBEA /* UIColor+Hex.swift in Sources */,
78CFEE5B1C5C45E500F50370 /* ViewModelProtocol.swift in Sources */,
EF5FB5691E0141610030E4BE /* UIView+Rotation.swift in Sources */,
67A7B1911EAF5F4900E5BC59 /* ImageDrawingOperation.swift in Sources */,
67A7B1931EAF5F6A00E5BC59 /* TemplateDrawingOperation.swift in Sources */,
783AF06D1E41CF5B00EC5ADE /* NetworkService.swift in Sources */,
780F56CC1E0D7ACA004530B6 /* ObservableMappable.swift in Sources */,
780D23431DA412470084620D /* CGImage+Alpha.swift in Sources */,
@ -1018,13 +1052,11 @@
67B856E31E923BE600F54304 /* ResettableType.swift in Sources */,
787783631CA03CA0001CDC9B /* IndexPath+ImmutableIndexPath.swift in Sources */,
675FB4251EA7797C0075BF3D /* Mutex.swift in Sources */,
78B036471DA5624D0021D5CC /* CGImage+Creation.swift in Sources */,
789CC6081DE5835600F789D3 /* CursorType.swift in Sources */,
67DC650F1E979D0C002F2FFF /* PaginationTableViewWrapperDelegate+DefaultImplementation.swift in Sources */,
67DC65041E979B34002F2FFF /* LoadingIndicatorProtocol.swift in Sources */,
78B0364B1DA61EDE0021D5CC /* CGImage+Crop.swift in Sources */,
EDF3DE3F1EA4F2E80016F729 /* UIViewController+DefaultXibName.swift in Sources */,
78B036451DA561D00021D5CC /* CGImage+Utils.swift in Sources */,
78CFEE591C5C45E500F50370 /* StoryboardIdentifierProtocol.swift in Sources */,
78011AB31D48B53600EA16A2 /* ApiRequestParameters.swift in Sources */,
676D17811EAE137B002E19F9 /* CGSize+Resize.swift in Sources */,

View File

@ -22,6 +22,15 @@
import CoreGraphics
extension CGBitmapInfo {
// The bitmapInfo value are hard-coded to prevent an "unsupported parameter combination" error
static let alphaBitmapInfo = CGBitmapInfo(rawValue: CGBitmapInfo().rawValue | CGImageAlphaInfo.premultipliedFirst.rawValue)
static let opaqueBitmapInfo = CGBitmapInfo(rawValue: CGBitmapInfo().rawValue | CGImageAlphaInfo.none.rawValue)
}
public extension CGContext {
/// Creates a bitmap graphics context with parameters taken from a given image.
@ -53,7 +62,7 @@ public extension CGContext {
/// - Returns: A new bitmap context, or NULL if a context could not be created.
public static func create(width: Int,
height: Int,
bitmapInfo: CGBitmapInfo = alphaBitmapInfo,
bitmapInfo: CGBitmapInfo = .alphaBitmapInfo,
colorSpace: CGColorSpace = CGColorSpaceCreateDeviceRGB(),
bitsPerComponent: Int = 8) -> CGContext? {

View File

@ -34,19 +34,4 @@ public extension CGImage {
}
}
/// Adds an alpha channel to image if it doesn't already have one.
///
/// - Returns: A copy of the given image, adding an alpha channel if it doesn't already have one
/// or nil if something goes wrong.
public func applyAlpha() -> CGImage? {
guard !hasAlpha else {
return self
}
let ctx = CGContext.create(width: width, height: height, bitmapInfo: alphaBitmapInfo)
ctx?.draw(self, in: bounds)
return ctx?.makeImage()
}
}

View File

@ -1,76 +0,0 @@
//
// Copyright (c) 2017 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import CoreGraphics
public extension CGImage {
/// Creates new CGImage instance filled by given color.
///
/// - Parameters:
/// - color: Color to fill.
/// - width: Width of new image.
/// - height: Height of new image.
/// - opaque: A flag indicating whether the bitmap is opaque (default: false).
/// - Returns: A new instanse of UIImage with given size and color or nil if something goes wrong.
public static func create(color: CGColor,
width: Int,
height: Int,
opaque: Bool = false) -> CGImage? {
let context = CGContext.create(width: width,
height: height,
bitmapInfo: opaque ? opaqueBitmapInfo : alphaBitmapInfo)
guard let ctx = context else {
return nil
}
ctx.setFillColor(color)
ctx.fill(CGRect(origin: CGPoint.zero, size: CGSize(width: width, height: height)))
return ctx.makeImage()
}
/// Creates an image from a given UIView.
///
/// - Parameter view: The source view.
/// - Returns: A new image created from the given view or nil if something goes wrong.
public static func create(fromView view: UIView) -> CGImage? {
let size = view.bounds.size
let ctxWidth = Int(ceil(size.width))
let ctxHeight = Int(ceil(size.height))
guard let ctx = CGContext.create(width: ctxWidth, height: ctxHeight) else {
return nil
}
ctx.translateBy(x: 0, y: size.height)
ctx.scaleBy(x: 1.0, y: -1.0)
view.layer.render(in: ctx)
return ctx.makeImage()
}
}

View File

@ -1,157 +0,0 @@
//
// Copyright (c) 2017 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import CoreGraphics
public extension CGImage {
/// Creates a new image with rounded corners.
///
/// - Parameter radius: The corner radius.
/// - Returns: New image with rounded corners or nil if something goes wrong.
public func round(withRadius radius: CGFloat) -> CGImage? {
guard let ctx = CGContext.create(forCGImage: self) ?? CGContext.create(width: width, height: height) else {
return nil
}
ctx.addPath(UIBezierPath(roundedRect: bounds, cornerRadius: radius).cgPath)
ctx.clip()
ctx.draw(self, in: bounds)
return ctx.makeImage()
}
/// Creates a new image with a border.
///
/// - Parameters:
/// - border: The size of the border.
/// - color: The color of the border.
/// - radius: The corner radius.
/// - extendSize: Extend result image size and don't overlap source image by border.
/// - Returns: A new image with border or nil if something goes wrong..
public func applyBorder(width border: CGFloat,
color: CGColor,
radius: CGFloat = 0,
extendSize: Bool = false) -> CGImage? {
let offset = extendSize ? border : 0
let newWidth = CGFloat(width) + offset * 2
let newHeight = CGFloat(height) + offset * 2
let ctxWidth = Int(ceil(newWidth))
let ctxHeight = Int(ceil(newHeight))
let ctxRect = CGRect(origin: CGPoint.zero, size: CGSize(width: newWidth, height: newHeight))
let context = CGContext.create(width: ctxWidth,
height: ctxHeight,
bitmapInfo: bitmapInfo,
colorSpace: colorSpace ?? CGColorSpaceCreateDeviceRGB(),
bitsPerComponent: bitsPerComponent)
guard let ctx = context ?? CGContext.create(width: width, height: height) else {
return nil
}
ctx.draw(self, in: CGRect(x: offset, y: offset, width: CGFloat(width), height: CGFloat(height)))
ctx.setStrokeColor(color)
let widthDiff = CGFloat(ctxWidth) - newWidth // difference between context width and real width
let heightDiff = CGFloat(ctxWidth) - newWidth // difference between context height and real height
let inset = ctxRect.insetBy(dx: border / 2 + widthDiff, dy: border / 2 + heightDiff)
if radius != 0 {
ctx.setLineWidth(border)
ctx.addPath(UIBezierPath(roundedRect: inset, cornerRadius: radius).cgPath)
ctx.strokePath()
} else {
ctx.stroke(inset, width: border)
}
return ctx.makeImage()
}
/// Creates a resized copy of an image.
///
/// - Parameters:
/// - newSize: The new size of the image.
/// - origin: The point where to place resized image
/// - Returns: A new resized image or nil if something goes wrong.
public func resize(to newSize: CGSize, usingOrigin origin: CGPoint = .zero) -> CGImage? {
let ctxWidth = Int(ceil(newSize.width))
let ctxHeight = Int(ceil(newSize.height))
let context = CGContext.create(width: ctxWidth,
height: ctxHeight,
bitmapInfo: bitmapInfo,
colorSpace: colorSpace ?? CGColorSpaceCreateDeviceRGB(),
bitsPerComponent: bitsPerComponent)
guard let ctx = context ?? CGContext.create(width: ctxWidth, height: ctxHeight) else {
return nil
}
let resizeRect = CGRect(origin: origin,
size: CGSize(width: ctxWidth, height: ctxHeight))
ctx.interpolationQuality = .high
ctx.draw(self, in: resizeRect)
return ctx.makeImage()
}
/// Creates a copy of the image with border of the given size added around its edges.
///
/// - Parameter padding: The padding amount.
/// - Returns: A new padded image or nil if something goes wrong..
public func applyPadding(_ padding: CGFloat) -> CGImage? {
let ctxWidth = Int(ceil(CGFloat(width) + padding * 2))
let ctxHeight = Int(ceil(CGFloat(height) + padding * 2))
let context = CGContext.create(width: ctxWidth,
height: ctxHeight,
bitmapInfo: bitmapInfo,
colorSpace: colorSpace ?? CGColorSpaceCreateDeviceRGB(),
bitsPerComponent: bitsPerComponent)
guard let ctx = context ?? CGContext.create(width: ctxWidth, height: ctxHeight) else {
return nil
}
// Draw the image in the center of the context, leaving a gap around the edges
let imageLocation = CGRect(x: padding,
y: padding,
width: CGFloat(width),
height: CGFloat(height))
ctx.addRect(imageLocation)
ctx.clip()
ctx.draw(self, in: imageLocation)
return ctx.makeImage()
}
}

View File

@ -0,0 +1,31 @@
//
// Copyright (c) 2017 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import CoreGraphics.CGGeometry
public extension CGSize {
var ceiledContextSize: CGContextSize {
return (width: Int(ceil(width)), height: Int(ceil(height)))
}
}

View File

@ -22,6 +22,7 @@
import UIKit
@available(iOS 10.0, *)
public extension UIImage {
/// Creates an image filled by given color.
@ -29,37 +30,41 @@ public extension UIImage {
/// - Parameters:
/// - color: The color to fill
/// - size: The size of an new image.
/// - Returns: A new instanse of UIImage with given size and color or nil if something goes wrong.
public convenience init?(color: UIColor, size: CGSize) {
let cgImage = CGImage.create(color: color.cgColor,
width: Int(ceil(size.width)),
height: Int(ceil(size.height)))
/// - scale: The scale of image.
/// - Returns: A new instanse of UIImage with given size and color.
public static func imageWith(color: UIColor, size: CGSize) -> UIImage {
let width = Int(ceil(size.width))
let height = Int(ceil(size.height))
guard let image = cgImage else {
return nil
}
let operation = SolidFillDrawingOperation(color: color.cgColor, width: width, height: height)
self.init(cgImage: image)
return operation.imageFromNewRenderer(scale: UIScreen.main.scale)
}
/// Creates an image from a UIView.
///
/// - Parameter fromView: The source view.
/// - Returns: A new instance of UIImage or nil if something goes wrong.
public convenience init?(fromView view: UIView) {
guard let cgImage = CGImage.create(fromView: view) else {
return nil
}
public static func imageFrom(view: UIView) -> UIImage {
let operation = CALayerDrawingOperation(layer: view.layer, size: view.bounds.size)
self.init(cgImage: cgImage)
return operation.imageFromNewRenderer(scale: UIScreen.main.scale)
}
/// Render current template UIImage into new image using given color.
///
/// - Parameter color: Color to fill template image.
/// - Returns: A new UIImage rendered with given color or nil if something goes wrong.
public func renderTemplate(withColor color: UIColor) -> UIImage? {
return cgImage?.renderTemplate(withColor: color.cgColor)?.uiImage
/// - Returns: A new UIImage rendered with given color.
public func renderTemplate(withColor color: UIColor) -> UIImage {
guard let image = cgImage else {
return self
}
let operation = TemplateDrawingOperation(image: image,
imageSize: size,
color: color.cgColor)
return operation.imageFromNewRenderer(scale: scale)
}
/// Creates a new image with rounded corners and border.
@ -69,25 +74,49 @@ public extension UIImage {
/// - borderWidth: The size of the border.
/// - color: The color of the border.
/// - extendSize: Extend result image size and don't overlap source image by border.
/// - Returns: A new image or nil if something goes wrong.
/// - Returns: A new image with rounded corners.
public func roundCorners(cornerRadius: CGFloat,
borderWidth: CGFloat,
color: UIColor,
extendSize: Bool = false) -> UIImage? {
extendSize: Bool = false) -> UIImage {
let rounded = cgImage?.round(withRadius: cornerRadius)
guard let image = cgImage else {
return self
}
return rounded?.applyBorder(width: borderWidth,
color: color.cgColor,
radius: cornerRadius,
extendSize: extendSize)?.uiImage
let roundOperation = RoundDrawingOperation(image: image,
imageSize: size,
radius: cornerRadius)
guard let roundImage = roundOperation.imageFromNewRenderer(scale: scale).cgImage else {
return self
}
let borderOperation = BorderDrawingOperation(image: roundImage,
imageSize: size,
border: borderWidth,
color: color.cgColor,
radius: cornerRadius,
extendSize: extendSize)
return borderOperation.imageFromNewRenderer(scale: scale)
}
/// Creates a new circle image.
///
/// - Returns: A new circled image or nil if something goes wrong.
public func roundCornersToCircle() -> UIImage? {
return cgImage?.round(withRadius: CGFloat(min(size.width, size.height) / 2))?.uiImage
/// - Returns: A new circled image.
public func roundCornersToCircle() -> UIImage {
guard let image = cgImage else {
return self
}
let radius = CGFloat(min(size.width, size.height) / 2)
let operation = RoundDrawingOperation(image: image,
imageSize: size,
radius: radius)
return operation.imageFromNewRenderer(scale: scale)
}
/// Creates a new circle image with a border.
@ -99,15 +128,30 @@ public extension UIImage {
/// - Returns: A new image with rounded corners or nil if something goes wrong.
public func roundCornersToCircle(borderWidth: CGFloat,
borderColor: UIColor,
extendSize: Bool = false) -> UIImage? {
extendSize: Bool = false) -> UIImage {
guard let image = cgImage else {
return self
}
let radius = CGFloat(min(size.width, size.height) / 2)
let rounded = cgImage?.round(withRadius: radius)
return rounded?.applyBorder(width: borderWidth,
color: borderColor.cgColor,
radius: radius,
extendSize: extendSize)?.uiImage
let roundOperation = RoundDrawingOperation(image: image,
imageSize: size,
radius: radius)
guard let roundImage = roundOperation.imageFromNewRenderer(scale: scale).cgImage else {
return self
}
let borderOperation = BorderDrawingOperation(image: roundImage,
imageSize: size,
border: borderWidth,
color: borderColor.cgColor,
radius: radius,
extendSize: extendSize)
return borderOperation.imageFromNewRenderer(scale: scale)
}
/// Creates a resized copy of an image.
@ -116,71 +160,84 @@ public extension UIImage {
/// - newSize: The new size of the image.
/// - contentMode: The way to handle the content in the new size.
/// - Returns: A new image scaled to new size.
public func resize(newSize: CGSize, contentMode: ResizeMode = .scaleToFill) -> UIImage? {
public func resize(newSize: CGSize, contentMode: ResizeMode = .scaleToFill) -> UIImage {
guard let image = cgImage else {
return self
}
let resizedRect = size.resizeRect(forNewSize: newSize, resizeMode: contentMode)
return cgImage?.resize(to: resizedRect.size, usingOrigin: resizedRect.origin)?.uiImage
let operation = ImageDrawingOperation(image: image,
newSize: resizedRect.size,
origin: resizedRect.origin)
return operation.imageFromNewRenderer(scale: scale)
}
/// Creates a resized copy of image using scaleAspectFit strategy.
///
/// - Parameter preferredNewSize: The preferred new size of image.
/// - Returns: A new image which size may be less then or equals to given preferredSize.
public func resizeAspectFit(preferredNewSize: CGSize) -> UIImage? {
let resizeRect = size.resizeRect(forNewSize: preferredNewSize, resizeMode: .scaleAspectFit)
public func resizeAspectFit(preferredNewSize: CGSize) -> UIImage {
guard let image = cgImage else {
return self
}
return cgImage?.resize(to: resizeRect.size)?.uiImage
}
let resizedRect = size.resizeRect(forNewSize: preferredNewSize, resizeMode: .scaleAspectFit)
/// Creates a cropped copy of image using the data contained within a subregion of an existing image.
///
/// - Parameter bounds: A rectangle whose coordinates specify the area to create an image from.
/// - Returns: A new image that specifies a subimage of the image.
/// If the rect parameter defines an area that is not in the image, returns NULL.
public func crop(to bounds: CGRect) -> UIImage? {
return cgImage?.cropping(to: bounds)?.uiImage
}
let operation = ImageDrawingOperation(image: image,
newSize: resizedRect.size,
origin: .zero)
/// Crop image with given margin values.
///
/// - Parameters:
/// - top: Top margin.
/// - left: Left margin.
/// - bottom: Bottom margin.
/// - right: Right margin.
/// - Returns: A new image cropped with given paddings or nil if something goes wrong.
public func crop(top: CGFloat = 0,
left: CGFloat = 0,
bottom: CGFloat = 0,
right: CGFloat = 0) -> UIImage? {
return cgImage?.crop(top: top,
left: left,
bottom: bottom,
right: right)?.uiImage
}
/// Crop image to square from center.
///
/// - Returns: A new cropped image.
public func cropFromCenterToSquare() -> UIImage? {
return cgImage?.cropFromCenterToSquare()?.uiImage
return operation.imageFromNewRenderer(scale: scale)
}
/// Adds an alpha channel if UIImage doesn't already have one.
///
/// - Returns: A copy of the given image, adding an alpha channel if it doesn't already have one.
public func applyAlpha() -> UIImage? {
return cgImage?.applyAlpha()?.uiImage
public func applyAlpha() -> UIImage {
guard let image = cgImage, !image.hasAlpha else {
return self
}
let operation = ImageDrawingOperation(image: image,
newSize: size,
opaque: false)
return operation.imageFromNewRenderer(scale: scale)
}
/// Creates a copy of the image with border of the given size added around its edges.
///
/// - Parameter padding: The padding amount.
/// - Returns: A new padded image or nil if something goes wrong.
public func applyPadding(_ padding: CGFloat) -> UIImage {
guard let image = cgImage else {
return self
}
let operation = PaddingDrawingOperation(image: image, imageSize: size, padding: padding)
return operation.imageFromNewRenderer(scale: scale)
}
}
public extension CGImage {
@available(iOS 10.0, *)
private extension DrawingOperation {
/// Creates new UIImage instance from CGImage.
public var uiImage: UIImage {
return UIImage(cgImage: self)
func imageFromNewRenderer(scale: CGFloat) -> UIImage {
let ctxSize = contextSize
let size = CGSize(width: ctxSize.width, height: ctxSize.height)
let format = UIGraphicsImageRendererFormat()
format.opaque = opaque
format.scale = scale
let renderer = UIGraphicsImageRenderer(size: size, format: format)
return renderer.image { context in
self.apply(in: context.cgContext)
}
}
}

View File

@ -0,0 +1,256 @@
//
// Copyright (c) 2017 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import UIKit
public extension Support where Base: UIImage {
/// Creates an image filled by given color.
///
/// - Parameters:
/// - color: The color to fill
/// - size: The size of an new image.
/// - Returns: A new instanse of UIImage with given size and color or nil if something goes wrong.
public static func imageWith(color: UIColor, size: CGSize) -> UIImage? {
let width = Int(ceil(size.width))
let height = Int(ceil(size.height))
let operation = SolidFillDrawingOperation(color: color.cgColor, width: width, height: height)
return operation.imageFromNewContext(scale: UIScreen.main.scale)
}
/// Creates an image from a UIView.
///
/// - Parameter fromView: The source view.
/// - Returns: A new instance of UIImage or nil if something goes wrong.
public static func imageFrom(view: UIView) -> UIImage? {
let operation = CALayerDrawingOperation(layer: view.layer, size: view.bounds.size)
return operation.imageFromNewContext(scale: UIScreen.main.scale)
}
/// Render current template UIImage into new image using given color.
///
/// - Parameter color: Color to fill template image.
/// - Returns: A new UIImage rendered with given color or nil if something goes wrong.
public func renderTemplate(withColor color: UIColor) -> UIImage? {
guard let image = base.cgImage else {
return base
}
let operation = TemplateDrawingOperation(image: image,
imageSize: base.size,
color: color.cgColor)
return operation.imageFromNewContext(scale: base.scale)
}
/// Creates a new image with rounded corners and border.
///
/// - Parameters:
/// - cornerRadius: The corner radius.
/// - borderWidth: The size of the border.
/// - color: The color of the border.
/// - extendSize: Extend result image size and don't overlap source image by border.
/// - Returns: A new image with rounded corners or nil if something goes wrong.
public func roundCorners(cornerRadius: CGFloat,
borderWidth: CGFloat,
color: UIColor,
extendSize: Bool = false) -> UIImage? {
guard let image = base.cgImage else {
return base
}
let roundOperation = RoundDrawingOperation(image: image,
imageSize: base.size,
radius: cornerRadius)
guard let roundImage = roundOperation.cgImageFromNewContext(scale: base.scale) else {
return nil
}
let borderOperation = BorderDrawingOperation(image: roundImage,
imageSize: base.size,
border: borderWidth,
color: color.cgColor,
radius: cornerRadius,
extendSize: extendSize)
return borderOperation.imageFromNewContext(scale: base.scale)
}
/// Creates a new circle image.
///
/// - Returns: A new circled image or nil if something goes wrong.
public func roundCornersToCircle() -> UIImage? {
guard let image = base.cgImage else {
return base
}
let radius = CGFloat(min(base.size.width, base.size.height) / 2)
let operation = RoundDrawingOperation(image: image,
imageSize: base.size,
radius: radius)
return operation.imageFromNewContext(scale: base.scale)
}
/// Creates a new circle image with a border.
///
/// - Parameters:
/// - borderWidth: The size of the border.
/// - borderColor: The color of the border.
/// - extendSize: Extend result image size and don't overlap source image by border (default = false).
/// - Returns: A new image with rounded corners or nil if something goes wrong.
public func roundCornersToCircle(borderWidth: CGFloat,
borderColor: UIColor,
extendSize: Bool = false) -> UIImage? {
guard let image = base.cgImage else {
return base
}
let radius = CGFloat(min(base.size.width, base.size.height) / 2)
let roundOperation = RoundDrawingOperation(image: image,
imageSize: base.size,
radius: radius)
guard let roundImage = roundOperation.cgImageFromNewContext(scale: base.scale) else {
return nil
}
let borderOperation = BorderDrawingOperation(image: roundImage,
imageSize: base.size,
border: borderWidth,
color: borderColor.cgColor,
radius: radius,
extendSize: extendSize)
return borderOperation.imageFromNewContext(scale: base.scale)
}
/// Creates a resized copy of an image.
///
/// - Parameters:
/// - newSize: The new size of the image.
/// - contentMode: The way to handle the content in the new size.
/// - Returns: A new image scaled to new size.
public func resize(newSize: CGSize, contentMode: ResizeMode = .scaleToFill) -> UIImage? {
guard let image = base.cgImage else {
return base
}
let resizedRect = base.size.resizeRect(forNewSize: newSize, resizeMode: contentMode)
let operation = ImageDrawingOperation(image: image,
newSize: resizedRect.size,
origin: resizedRect.origin)
return operation.imageFromNewContext(scale: base.scale)
}
/// Creates a resized copy of image using scaleAspectFit strategy.
///
/// - Parameter preferredNewSize: The preferred new size of image.
/// - Returns: A new image which size may be less then or equals to given preferredSize.
public func resizeAspectFit(preferredNewSize: CGSize) -> UIImage? {
guard let image = base.cgImage else {
return base
}
let resizedRect = base.size.resizeRect(forNewSize: preferredNewSize, resizeMode: .scaleAspectFit)
let operation = ImageDrawingOperation(image: image,
newSize: resizedRect.size,
origin: .zero)
return operation.imageFromNewContext(scale: base.scale)
}
/// Adds an alpha channel if UIImage doesn't already have one.
///
/// - Returns: A copy of the given image, adding an alpha channel if it doesn't already have one.
public func applyAlpha() -> UIImage? {
guard let image = base.cgImage, !image.hasAlpha else {
return base
}
let operation = ImageDrawingOperation(image: image,
newSize: base.size,
opaque: false)
return operation.imageFromNewContext(scale: base.scale)
}
/// Creates a copy of the image with border of the given size added around its edges.
///
/// - Parameter padding: The padding amount.
/// - Returns: A new padded image or nil if something goes wrong.
public func applyPadding(_ padding: CGFloat) -> UIImage? {
guard let image = base.cgImage else {
return base
}
let operation = PaddingDrawingOperation(image: image,
imageSize: base.size,
padding: padding)
return operation.imageFromNewContext(scale: base.scale)
}
}
private extension DrawingOperation {
func cgImageFromNewContext(scale: CGFloat) -> CGImage? {
let ctxSize = contextSize
let intScale = Int(scale)
let context = CGContext.create(width: ctxSize.width * intScale,
height: ctxSize.height * intScale,
bitmapInfo: opaque ? .opaqueBitmapInfo : .alphaBitmapInfo)
guard let ctx = context else {
return nil
}
ctx.scaleBy(x: scale, y: scale)
apply(in: ctx)
return ctx.makeImage()
}
func imageFromNewContext(scale: CGFloat) -> UIImage? {
guard let image = cgImageFromNewContext(scale: scale) else {
return nil
}
return UIImage(cgImage: image, scale: scale, orientation: .up)
}
}

View File

@ -2,7 +2,7 @@
// Copyright (c) 2017 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
@ -11,7 +11,7 @@
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
@ -22,29 +22,29 @@
import CoreGraphics
public extension CGImage {
public typealias CGContextSize = (width: Int, height: Int)
/// Renders current template CGImage into new image using given color.
/// Abstract drawing operation that can can be applied in CGContext.
/// Reports required size and opaque attibute for context.
public protocol DrawingOperation {
/// Required context size.
var contextSize: CGContextSize { get }
/// Reports: should context be opaque.
var opaque: Bool { get }
/// Applies drawing operation in given CGContext instance.
///
/// - Parameter color: Color used to fill template image
/// - Returns: A new CGImage instance rendered with given color or nil if something goes wrong.
public func renderTemplate(withColor color: CGColor) -> CGImage? {
guard let ctx = CGContext.create(forCGImage: self) ?? CGContext.create(width: width, height: height) else {
return nil
}
/// - Parameter context: CGContext to perform drawing manipulations.
func apply(in context: CGContext)
let imageRect = bounds
}
ctx.setFillColor(color)
extension DrawingOperation {
ctx.translateBy(x: 0, y: CGFloat(height))
ctx.scaleBy(x: 1.0, y: -1.0)
ctx.clip(to: imageRect, mask: self)
ctx.fill(imageRect)
ctx.setBlendMode(.multiply)
return ctx.makeImage()
var opaque: Bool {
return false
}
}

View File

@ -0,0 +1,88 @@
//
// Copyright (c) 2017 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import CoreGraphics
struct BorderDrawingOperation: DrawingOperation {
private let image: CGImage
private let imageSize: CGSize
private let border: CGFloat
private let color: CGColor
private let radius: CGFloat
private let extendSize: Bool
public init(image: CGImage,
imageSize: CGSize,
border: CGFloat,
color: CGColor,
radius: CGFloat,
extendSize: Bool) {
self.image = image
self.imageSize = imageSize
self.border = border
self.color = color
self.radius = radius
self.extendSize = extendSize
}
public var contextSize: CGContextSize {
let offset = extendSize ? border : 0
let width = imageSize.width + offset * 2
let height = imageSize.height + offset * 2
return (width: Int(ceil(width)), height: Int(ceil(height)))
}
public func apply(in context: CGContext) {
let offset = extendSize ? border : 0
let newWidth = imageSize.width + offset * 2
let newHeight = imageSize.height + offset * 2
let ctxSize = contextSize
let ctxRect = CGRect(origin: .zero, size: CGSize(width: ctxSize.width, height: ctxSize.height))
let imageRect = CGRect(x: offset, y: offset, width: imageSize.width, height: imageSize.height)
context.draw(image, in: imageRect)
context.setStrokeColor(color)
let widthDiff = CGFloat(ctxSize.width) - newWidth // difference between context width and real width
let heightDiff = CGFloat(ctxSize.height) - newHeight // difference between context height and real height
let inset = ctxRect.insetBy(dx: border / 2 + widthDiff, dy: border / 2 + heightDiff)
if radius != 0 {
context.setLineWidth(border)
context.addPath(UIBezierPath(roundedRect: inset, cornerRadius: radius).cgPath)
context.strokePath()
} else {
context.stroke(inset, width: border)
}
}
}

View File

@ -2,7 +2,7 @@
// Copyright (c) 2017 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
@ -11,7 +11,7 @@
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
@ -22,21 +22,25 @@
import CoreGraphics
// The bitmapInfo value are hard-coded to prevent an "unsupported parameter combination" error
struct CALayerDrawingOperation: DrawingOperation {
public let alphaBitmapInfo = CGBitmapInfo(rawValue: CGBitmapInfo().rawValue | CGImageAlphaInfo.premultipliedFirst.rawValue)
public let opaqueBitmapInfo = CGBitmapInfo(rawValue: CGBitmapInfo().rawValue | CGImageAlphaInfo.none.rawValue)
private let layer: CALayer
private let size: CGSize
public extension CGImage {
/// Size of image.
var size: CGSize {
return CGSize(width: width, height: height)
public init(layer: CALayer, size: CGSize) {
self.layer = layer
self.size = size
}
/// Bounds of image.
var bounds: CGRect {
return CGRect(origin: .zero, size: size)
public var contextSize: CGContextSize {
return size.ceiledContextSize
}
public func apply(in context: CGContext) {
context.translateBy(x: 0, y: size.height)
context.scaleBy(x: 1.0, y: -1.0)
layer.render(in: context)
}
}

View File

@ -0,0 +1,48 @@
//
// Copyright (c) 2017 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import CoreGraphics
struct ImageDrawingOperation: DrawingOperation {
private let image: CGImage
private let newSize: CGSize
private let origin: CGPoint
public let opaque: Bool
public init(image: CGImage, newSize: CGSize, origin: CGPoint = .zero, opaque: Bool = true) {
self.image = image
self.newSize = newSize
self.origin = origin
self.opaque = opaque
}
public var contextSize: CGContextSize {
return newSize.ceiledContextSize
}
public func apply(in context: CGContext) {
context.interpolationQuality = .high
context.draw(image, in: CGRect(origin: origin, size: newSize))
}
}

View File

@ -0,0 +1,57 @@
//
// Copyright (c) 2017 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import CoreGraphics
struct PaddingDrawingOperation: DrawingOperation {
private let image: CGImage
private let imageSize: CGSize
private let padding: CGFloat
public init(image: CGImage, imageSize: CGSize, padding: CGFloat) {
self.image = image
self.imageSize = imageSize
self.padding = padding
}
public var contextSize: CGContextSize {
let width = Int(ceil(imageSize.width + padding * 2))
let height = Int(ceil(imageSize.height + padding * 2))
return (width: width, height: height)
}
public func apply(in context: CGContext) {
// Draw the image in the center of the context, leaving a gap around the edges
let imageLocation = CGRect(x: padding,
y: padding,
width: imageSize.width,
height: imageSize.height)
context.addRect(imageLocation)
context.clip()
context.draw(image, in: imageLocation)
}
}

View File

@ -0,0 +1,49 @@
//
// Copyright (c) 2017 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import CoreGraphics
struct RoundDrawingOperation: DrawingOperation {
private let image: CGImage
private let imageSize: CGSize
private let radius: CGFloat
public init(image: CGImage, imageSize: CGSize, radius: CGFloat) {
self.image = image
self.imageSize = imageSize
self.radius = radius
}
public var contextSize: CGContextSize {
return imageSize.ceiledContextSize
}
public func apply(in context: CGContext) {
let imageLocation = CGRect(origin: .zero, size: imageSize)
context.addPath(UIBezierPath(roundedRect: imageLocation, cornerRadius: radius).cgPath)
context.clip()
context.draw(image, in: imageLocation)
}
}

View File

@ -0,0 +1,46 @@
//
// Copyright (c) 2017 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import CoreGraphics
struct SolidFillDrawingOperation: DrawingOperation {
private let color: CGColor
private let width: Int
private let height: Int
public init(color: CGColor, width: Int, height: Int) {
self.color = color
self.width = width
self.height = height
}
public var contextSize: CGContextSize {
return (width: width, height: height)
}
public func apply(in context: CGContext) {
context.setFillColor(color)
context.fill(CGRect(origin: .zero, size: CGSize(width: width, height: height)))
}
}

View File

@ -0,0 +1,54 @@
//
// Copyright (c) 2017 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import CoreGraphics
struct TemplateDrawingOperation: DrawingOperation {
private let image: CGImage
private let imageSize: CGSize
private let color: CGColor
public init(image: CGImage, imageSize: CGSize, color: CGColor) {
self.image = image
self.imageSize = imageSize
self.color = color
}
public var contextSize: CGContextSize {
return imageSize.ceiledContextSize
}
public func apply(in context: CGContext) {
let imageRect = CGRect(origin: .zero, size: imageSize)
context.setFillColor(color)
context.translateBy(x: 0, y: imageSize.height)
context.scaleBy(x: 1.0, y: -1.0)
context.clip(to: imageRect, mask: image)
context.fill(imageRect)
context.setBlendMode(.multiply)
}
}