7 Commits

Author SHA1 Message Date
Alex K 96abfee256 converted to swift 5 2019-04-15 11:28:19 +03:00
i.kolpachkov 86a06411d7 [Toggler control]: small improvements 2018-12-10 01:38:34 +03:00
i.kolpachkov 8031f5a28f add collection layouts, string & color extensions 2018-11-26 23:26:16 +03:00
i.kolpachkov fdd2ed5b7c small improvement 2018-11-23 17:25:56 +03:00
i.kolpachkov 5cabfab574 UIColor extensions 2018-11-23 02:16:09 +03:00
i.kolpachkov 535b76fea0 Merge branch 'master' of https://github.com/Ramotion/utopia 2018-11-21 22:27:13 +03:00
i.kolpachkov 5b0672ee60 Core Graphics extensions 2018-11-21 22:26:59 +03:00
29 changed files with 1764 additions and 177 deletions
+35 -16
View File
@@ -36,6 +36,12 @@
391DB7D6215317A20083B8C3 /* TouchInteraction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 391DB7D5215317A20083B8C3 /* TouchInteraction.swift */; };
3948F8B92193A3000043FD2E /* PinnedHeaderFlowLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3948F8B82193A3000043FD2E /* PinnedHeaderFlowLayout.swift */; };
398EF3F919D8AFA5093B15156F27E353 /* AssociatedObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = F90027142D498AD8ABA5215ADC06AC80 /* AssociatedObject.swift */; };
399D418F21AC8D1C00A0B1EA /* UIColor+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 399D418D21AC8D1200A0B1EA /* UIColor+Extensions.swift */; };
399D419421AC8DCF00A0B1EA /* NSAttributedString+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 399D419021AC8DC300A0B1EA /* NSAttributedString+Extensions.swift */; };
399D419521AC8DCF00A0B1EA /* String+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 399D419121AC8DC300A0B1EA /* String+Extensions.swift */; };
399D419921AC8E0D00A0B1EA /* VerticalAlignmentLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 399D419621AC8E0C00A0B1EA /* VerticalAlignmentLayout.swift */; };
399D419A21AC8E0D00A0B1EA /* PaginationFlowLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 399D419721AC8E0D00A0B1EA /* PaginationFlowLayout.swift */; };
399D419B21AC8E0D00A0B1EA /* SnappingFlowLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 399D419821AC8E0D00A0B1EA /* SnappingFlowLayout.swift */; };
3C087E90B6D5631B925B524FBD183DA5 /* Associated.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43A88C1BE5A50F7B80FB3A7EF1DD75D5 /* Associated.swift */; };
3C593180EE447D73A5ADBA3AE8909897 /* InputVisibilityController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80C3D37E1C6C9537466FBA3F006E1A2D /* InputVisibilityController.swift */; };
40077E1D3AAA704AC97E1AEDA22CF464 /* StatusBarAppearance.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C4EEBAEB1E1FDB6EDA95CFF0B7ECFA7 /* StatusBarAppearance.swift */; };
@@ -70,9 +76,7 @@
C1EA2BF250F881A8CE274F56698CA5ED /* Hapticable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 352DCA45D28C00C59D68C1E66313DA94 /* Hapticable.swift */; };
C5B3A325A391083BCBE0225D0E492651 /* AlphaTransition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 73F563779A49E6F8BABD9F7F096FAE38 /* AlphaTransition.swift */; };
C980A8B4B7D46F918161A80D523F5B9A /* Comparable+ext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DF39CDC694A39ABC8161159EFBF1969 /* Comparable+ext.swift */; };
CA706B65DFD60F03EDC1773828AC4306 /* UIColor+ext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 002CE560EE32DF3EA2939698D55CA6DC /* UIColor+ext.swift */; };
CA834F710C7B8DE7E78756369DB8B082 /* DateUtilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD719201D9EBBBFBC9D36540F14DFA52 /* DateUtilities.swift */; };
CAF36258A097CBCB956B2742329293F7 /* SwiftyColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55C7669A602A5ACC46EB4D109E37EBBD /* SwiftyColor.swift */; };
D5136D58B70ED60743594DDA623340A6 /* Result.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0B2707546E60D2905D4AF46DB62253F2 /* Result.swift */; };
D7D7B6285FF6691B27A7D73FF196468A /* Bundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 325E227EFA7126E2C5DD6AD776413857 /* Bundle.swift */; };
D91F78D2FCE584E9E3E945F8FDD0C80E /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6604A7D69453B4569E4E4827FB9155A9 /* Foundation.framework */; };
@@ -100,7 +104,6 @@
/* End PBXContainerItemProxy section */
/* Begin PBXFileReference section */
002CE560EE32DF3EA2939698D55CA6DC /* UIColor+ext.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = "UIColor+ext.swift"; sourceTree = "<group>"; };
03B1B68A1207D9BA0DEC85B5917051B3 /* Pods-utopia_Example-acknowledgements.markdown */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; path = "Pods-utopia_Example-acknowledgements.markdown"; sourceTree = "<group>"; };
04B8F1CF04C636E2B5F6107FCFE6A533 /* Snapshot.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = Snapshot.swift; sourceTree = "<group>"; };
0B2707546E60D2905D4AF46DB62253F2 /* Result.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = Result.swift; sourceTree = "<group>"; };
@@ -125,6 +128,12 @@
391DB7D3215317990083B8C3 /* GestureRecognizerDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GestureRecognizerDelegate.swift; sourceTree = "<group>"; };
391DB7D5215317A20083B8C3 /* TouchInteraction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TouchInteraction.swift; sourceTree = "<group>"; };
3948F8B82193A3000043FD2E /* PinnedHeaderFlowLayout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PinnedHeaderFlowLayout.swift; sourceTree = "<group>"; };
399D418D21AC8D1200A0B1EA /* UIColor+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIColor+Extensions.swift"; sourceTree = "<group>"; };
399D419021AC8DC300A0B1EA /* NSAttributedString+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSAttributedString+Extensions.swift"; sourceTree = "<group>"; };
399D419121AC8DC300A0B1EA /* String+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+Extensions.swift"; sourceTree = "<group>"; };
399D419621AC8E0C00A0B1EA /* VerticalAlignmentLayout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VerticalAlignmentLayout.swift; sourceTree = "<group>"; };
399D419721AC8E0D00A0B1EA /* PaginationFlowLayout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PaginationFlowLayout.swift; sourceTree = "<group>"; };
399D419821AC8E0D00A0B1EA /* SnappingFlowLayout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SnappingFlowLayout.swift; sourceTree = "<group>"; };
41498A7B2FB6C962AF2CAA1727FA4BEE /* UITableView+Reusable.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = "UITableView+Reusable.swift"; sourceTree = "<group>"; };
43A88C1BE5A50F7B80FB3A7EF1DD75D5 /* Associated.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = Associated.swift; sourceTree = "<group>"; };
474B741D8D941D2368F81BD923718887 /* Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
@@ -136,7 +145,6 @@
507F6CFD78D079631EF4C7414D722BE9 /* ScrollDismission.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ScrollDismission.swift; sourceTree = "<group>"; };
51A7A173B2BB4912B56024A0AB99C624 /* UISearchUtilities.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = UISearchUtilities.swift; sourceTree = "<group>"; };
53845654F739F2E9534B24DEC5A577AD /* SimpleError.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = SimpleError.swift; sourceTree = "<group>"; };
55C7669A602A5ACC46EB4D109E37EBBD /* SwiftyColor.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = SwiftyColor.swift; sourceTree = "<group>"; };
5C127A0077D1A43E0B1AF57D5C3E0EC4 /* Toggler.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = Toggler.swift; sourceTree = "<group>"; };
655F593EC54CC0CC958E764E3A09282D /* ImageSettable.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ImageSettable.swift; sourceTree = "<group>"; };
6604A7D69453B4569E4E4827FB9155A9 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS10.3.sdk/System/Library/Frameworks/Foundation.framework; sourceTree = DEVELOPER_DIR; };
@@ -223,7 +231,6 @@
isa = PBXGroup;
children = (
655F593EC54CC0CC958E764E3A09282D /* ImageSettable.swift */,
55C7669A602A5ACC46EB4D109E37EBBD /* SwiftyColor.swift */,
485554E3BDF22424E17D04F2C017B0C9 /* SwiftyImage.swift */,
496E51ADD4779D6AC8AEB0E4AC214241 /* SwiftyImageView.swift */,
);
@@ -272,6 +279,9 @@
children = (
3948F8B82193A3000043FD2E /* PinnedHeaderFlowLayout.swift */,
391DB7CD215316AB0083B8C3 /* HorizontalAlignmentLayout.swift */,
399D419721AC8E0D00A0B1EA /* PaginationFlowLayout.swift */,
399D419821AC8E0D00A0B1EA /* SnappingFlowLayout.swift */,
399D419621AC8E0C00A0B1EA /* VerticalAlignmentLayout.swift */,
);
path = CollectionLayout;
sourceTree = "<group>";
@@ -464,11 +474,13 @@
3519B7CEB0403B5FAF2E8610A97B1501 /* Require.swift */,
0B2707546E60D2905D4AF46DB62253F2 /* Result.swift */,
507F6CFD78D079631EF4C7414D722BE9 /* ScrollDismission.swift */,
399D419021AC8DC300A0B1EA /* NSAttributedString+Extensions.swift */,
399D419121AC8DC300A0B1EA /* String+Extensions.swift */,
399D418D21AC8D1200A0B1EA /* UIColor+Extensions.swift */,
53845654F739F2E9534B24DEC5A577AD /* SimpleError.swift */,
B7478349DD7D5B359C7BDCE6B82A1658 /* Then.swift */,
85C649278AE9FEC1B162C60ABAAE5123 /* Time.swift */,
FE5615B6D865408D6F06B2F71F6FA57D /* TimingFunction.swift */,
002CE560EE32DF3EA2939698D55CA6DC /* UIColor+ext.swift */,
);
name = Utilities;
path = utopia/Source/Utilities;
@@ -607,22 +619,23 @@
isa = PBXProject;
attributes = {
LastSwiftUpdateCheck = 0930;
LastUpgradeCheck = 0930;
LastUpgradeCheck = 1020;
TargetAttributes = {
02B55224E15D80EA5F1C45D2650F46E3 = {
LastSwiftMigration = 1000;
LastSwiftMigration = 1020;
};
55FF8012DB818DE68BDEF0BC12B62C22 = {
LastSwiftMigration = 1000;
LastSwiftMigration = 1020;
};
};
};
buildConfigurationList = 2D8E8EC45A3A1A1D94AE762CB5028504 /* Build configuration list for PBXProject "Pods" */;
compatibilityVersion = "Xcode 3.2";
developmentRegion = English;
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 7DB346D0F39D3F0E887471402A8071AB;
productRefGroup = 73E0200A5FD816721F62FE79BAD1BC01 /* Products */;
@@ -643,6 +656,7 @@
C5B3A325A391083BCBE0225D0E492651 /* AlphaTransition.swift in Sources */,
3C087E90B6D5631B925B524FBD183DA5 /* Associated.swift in Sources */,
398EF3F919D8AFA5093B15156F27E353 /* AssociatedObject.swift in Sources */,
399D418F21AC8D1C00A0B1EA /* UIColor+Extensions.swift in Sources */,
FBA69FEF5BF5077D1A63854B7286CC57 /* AsyncOperation.swift in Sources */,
DC9BC229563C9D4D76C4ADD9962E181A /* BarButtons.swift in Sources */,
D7D7B6285FF6691B27A7D73FF196468A /* Bundle.swift in Sources */,
@@ -668,6 +682,7 @@
5179A999CF3F2B5C221D379A978334B1 /* Optional+ext.swift in Sources */,
F844B62463F01BDF65C1553CD6670CC2 /* Random.swift in Sources */,
286B06BEE5CF5C9680D6C4E3F7C671D5 /* Reachability.swift in Sources */,
399D419421AC8DCF00A0B1EA /* NSAttributedString+Extensions.swift in Sources */,
9E1484918CFB62F8A4DBAFD2604BDBBC /* Require.swift in Sources */,
D5136D58B70ED60743594DDA623340A6 /* Result.swift in Sources */,
F40A972F66F6674CDEA0187860E8785B /* ScaleModalTransitioning.swift in Sources */,
@@ -678,10 +693,10 @@
35F36F6F8769CDC19189C6C44BECE03E /* SignalSubscription.swift in Sources */,
25625A79AD94F64B1C05606994AEC409 /* SimpleError.swift in Sources */,
29396B3ADC9D79BC235C37FC87898C0E /* Snapshot.swift in Sources */,
399D419521AC8DCF00A0B1EA /* String+Extensions.swift in Sources */,
40077E1D3AAA704AC97E1AEDA22CF464 /* StatusBarAppearance.swift in Sources */,
A848BE440EAD8450C7B15BE5DB31BAE7 /* String+ext.swift in Sources */,
09471D36F0C65BF373FE54C944C68820 /* StringValidators.swift in Sources */,
CAF36258A097CBCB956B2742329293F7 /* SwiftyColor.swift in Sources */,
269A0BE57E27A3DFB1063BB77EDE4935 /* SwiftyImage.swift in Sources */,
556DBB6B22CB0D57352504204588353C /* SwiftyImageView.swift in Sources */,
A3DA1CA55FB34B50280E9E24460A7C50 /* Then.swift in Sources */,
@@ -696,7 +711,6 @@
9052FD4E4A2BF4D796D2521EE47B2069 /* UIButton+layout.swift in Sources */,
0BE22BEB5AE38CEC94E0A184C8946969 /* UICollectionView+Reusable.swift in Sources */,
391DB7D0215316F50083B8C3 /* InteractiveDismiss.swift in Sources */,
CA706B65DFD60F03EDC1773828AC4306 /* UIColor+ext.swift in Sources */,
696FBE84EEB2A8F325E8D551903BF524 /* UIControl+Signals.swift in Sources */,
02807C6CD1D68F6A8F1D5F5EDBC075F3 /* UIGestureRecognizer+ext.swift in Sources */,
E5B2024891F21779A4AB81AB91A2A03D /* UIImage+ext.swift in Sources */,
@@ -722,7 +736,10 @@
buildActionMask = 2147483647;
files = (
3948F8B92193A3000043FD2E /* PinnedHeaderFlowLayout.swift in Sources */,
399D419A21AC8E0D00A0B1EA /* PaginationFlowLayout.swift in Sources */,
399D419921AC8E0D00A0B1EA /* VerticalAlignmentLayout.swift in Sources */,
5B190B9D09C4A639DA32D00681E1B4A7 /* Pods-utopia_Example-dummy.m in Sources */,
399D419B21AC8E0D00A0B1EA /* SnappingFlowLayout.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -768,7 +785,7 @@
SDKROOT = iphoneos;
SKIP_INSTALL = YES;
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
SWIFT_VERSION = 4.2;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
VERSIONING_SYSTEM = "apple-generic";
@@ -802,7 +819,7 @@
SKIP_INSTALL = YES;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) ";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 4.2;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
@@ -813,6 +830,7 @@
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
@@ -876,6 +894,7 @@
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
@@ -956,7 +975,7 @@
SKIP_INSTALL = YES;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) ";
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
SWIFT_VERSION = 4.2;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
VERSIONING_SYSTEM = "apple-generic";
@@ -995,7 +1014,7 @@
SKIP_INSTALL = YES;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 4.2;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
@@ -41,7 +41,7 @@ extension KeyedDecodingContainer {
}
public func decodeSafelyIfPresent<T: Decodable>(_ type: T.Type, forKey key: KeyedDecodingContainer.Key) -> T? {
let decoded = try? decodeIfPresent(Safe<T>.self, forKey: key)
let decoded = ((try? decodeIfPresent(Safe<T>.self, forKey: key)) as Safe<T>??)
return decoded??.value
}
}
@@ -67,10 +67,10 @@ public struct Id<Entity>: Hashable {
self.raw = raw
}
public var hashValue: Int {
return raw.hashValue
public func hash(into hasher: inout Hasher) {
hasher.combine(raw.hashValue)
}
public static func ==(lhs: Id, rhs: Id) -> Bool {
return lhs.raw == rhs.raw
}
@@ -2,7 +2,7 @@ import UIKit
public extension UIView {
public var backgroundImage: UIImage? {
var backgroundImage: UIImage? {
get {
guard let obj = layer.contents else { return nil }
return UIImage(cgImage: obj as! CGImage)
@@ -21,8 +21,8 @@ public extension UIView {
public extension UIView {
public final func updateOpaque() {
if let color = backgroundColor, color.alphaValue == 1.0, alpha == 1.0 {
final func updateOpaque() {
if let color = backgroundColor, color.rgba.a == 1.0, alpha == 1.0 {
isOpaque = true
}
else {
@@ -1,23 +0,0 @@
import UIKit
public extension UIColor
{
public final var redValue: CGFloat { return rgba().r }
public final var greenValue: CGFloat { return rgba().g }
public final var blueValue: CGFloat { return rgba().b }
public final var alphaValue: CGFloat { return rgba().a }
private final func rgba() -> (r: CGFloat, g: CGFloat, b: CGFloat, a: CGFloat)
{
guard let components: [CGFloat] = cgColor.components else { return (0, 0, 0, 0) }
let numberOfComponents: Int = cgColor.numberOfComponents
switch numberOfComponents {
case 4:
return (components[0], components[1], components[2], components[3])
case 2:
return (components[0], components[0], components[0], components[1])
default:
return (0, 0, 0, 1)
}
}
}
@@ -2,12 +2,12 @@ import UIKit
public extension UIImage
{
public final var isOpaque: Bool {
final var isOpaque: Bool {
let alphaInfo = cgImage?.alphaInfo
return !(alphaInfo == .first || alphaInfo == .last || alphaInfo == .premultipliedFirst || alphaInfo == .premultipliedLast)
}
public final func reSize(to size: CGSize) -> UIImage {
final func reSize(to size: CGSize) -> UIImage {
guard size.width > 0 && size.height > 0 else { return self }
UIGraphicsBeginImageContextWithOptions(size, isOpaque, 0.0)
@@ -18,7 +18,7 @@ public extension UIImage
return scaledImage
}
public final func reSize(toFit size: CGSize) -> UIImage {
final func reSize(toFit size: CGSize) -> UIImage {
guard size.width > 0 && size.height > 0 else { return self }
let imageAspectRatio = self.size.width / self.size.height
@@ -40,7 +40,7 @@ public extension UIImage
return scaledImage
}
public final func reSize(toFill size: CGSize) -> UIImage {
final func reSize(toFill size: CGSize) -> UIImage {
guard size.width > 0 && size.height > 0 else { return self }
let imageAspectRatio = self.size.width / self.size.height
@@ -62,7 +62,7 @@ public extension UIImage
return scaledImage
}
public final func rounded(withCornerRadius radius: CGFloat, divideRadiusByImageScale: Bool = false) -> UIImage {
final func rounded(withCornerRadius radius: CGFloat, divideRadiusByImageScale: Bool = false) -> UIImage {
UIGraphicsBeginImageContextWithOptions(size, false, 0.0)
let scaledRadius = divideRadiusByImageScale ? radius / scale : radius
@@ -78,7 +78,7 @@ public extension UIImage
return roundedImage
}
public final func roundedIntoCircle() -> UIImage {
final func roundedIntoCircle() -> UIImage {
let radius = min(size.width, size.height) / 2.0
var squareImage = self
if size.width != size.height {
@@ -26,7 +26,7 @@ public class SwiftyImageView: UIView {
public extension SwiftyImageView {
public enum ImageTransition {
enum ImageTransition {
case noTransition
case crossDissolve(TimeInterval)
case curlDown(TimeInterval)
@@ -109,7 +109,7 @@ public extension SwiftyImageView {
}
}
public final func transition(_ imageTransition: ImageTransition, with image: UIImage) {
final func transition(_ imageTransition: ImageTransition, with image: UIImage) {
UIView.transition(with: self, duration: imageTransition.duration, options: imageTransition.animationOptions, animations: { imageTransition.animations(self, image) }, completion: imageTransition.completion)
}
@@ -13,7 +13,7 @@ import UIKit
/// Extends UIBarButtonItem with signal for the action.
public extension UIBarButtonItem {
/// A signal that fires for each action event.
public var onAction: Signal<(Void)> {
var onAction: Signal<(Void)> {
return getOrCreateSignal();
}
+15 -15
View File
@@ -9,77 +9,77 @@ import UIKit
/// Extends UIControl with signals for all ui control events.
public extension UIControl {
/// A signal that fires for each touch down control event.
public var onTouchDown: Signal<(Void)> {
var onTouchDown: Signal<(Void)> {
return getOrCreateSignalForUIControlEvent(.touchDown);
}
/// A signal that fires for each touch down repeat control event.
public var onTouchDownRepeat: Signal<(Void)> {
var onTouchDownRepeat: Signal<(Void)> {
return getOrCreateSignalForUIControlEvent(.touchDownRepeat);
}
/// A signal that fires for each touch drag inside control event.
public var onTouchDragInside: Signal<(Void)> {
var onTouchDragInside: Signal<(Void)> {
return getOrCreateSignalForUIControlEvent(.touchDragInside);
}
/// A signal that fires for each touch drag outside control event.
public var onTouchDragOutside: Signal<(Void)> {
var onTouchDragOutside: Signal<(Void)> {
return getOrCreateSignalForUIControlEvent(.touchDragOutside);
}
/// A signal that fires for each touch drag enter control event.
public var onTouchDragEnter: Signal<(Void)> {
var onTouchDragEnter: Signal<(Void)> {
return getOrCreateSignalForUIControlEvent(.touchDragEnter);
}
/// A signal that fires for each touch drag exit control event.
public var onTouchDragExit: Signal<(Void)> {
var onTouchDragExit: Signal<(Void)> {
return getOrCreateSignalForUIControlEvent(.touchDragExit);
}
/// A signal that fires for each touch up inside control event.
public var onTouchUpInside: Signal<(Void)> {
var onTouchUpInside: Signal<(Void)> {
return getOrCreateSignalForUIControlEvent(.touchUpInside);
}
/// A signal that fires for each primary action control event.
public var onPrimaryActionTriggered: Signal<(Void)> {
var onPrimaryActionTriggered: Signal<(Void)> {
return getOrCreateSignalForUIControlEvent(.primaryActionTriggered);
}
/// A signal that fires for each touch up outside control event.
public var onTouchUpOutside: Signal<(Void)> {
var onTouchUpOutside: Signal<(Void)> {
return getOrCreateSignalForUIControlEvent(.touchUpOutside);
}
/// A signal that fires for each touch cancel control event.
public var onTouchCancel: Signal<(Void)> {
var onTouchCancel: Signal<(Void)> {
return getOrCreateSignalForUIControlEvent(.touchCancel);
}
/// A signal that fires for each value changed control event.
public var onValueChanged: Signal<(Void)> {
var onValueChanged: Signal<(Void)> {
return getOrCreateSignalForUIControlEvent(.valueChanged);
}
/// A signal that fires for each editing did begin control event.
public var onEditingDidBegin: Signal<(Void)> {
var onEditingDidBegin: Signal<(Void)> {
return getOrCreateSignalForUIControlEvent(.editingDidBegin);
}
/// A signal that fires for each editing changed control event.
public var onEditingChanged: Signal<(Void)> {
var onEditingChanged: Signal<(Void)> {
return getOrCreateSignalForUIControlEvent(.editingChanged);
}
/// A signal that fires for each editing did end control event.
public var onEditingDidEnd: Signal<(Void)> {
var onEditingDidEnd: Signal<(Void)> {
return getOrCreateSignalForUIControlEvent(.editingDidEnd);
}
/// A signal that fires for each editing did end on exit control event.
public var onEditingDidEndOnExit: Signal<(Void)> {
var onEditingDidEndOnExit: Signal<(Void)> {
return getOrCreateSignalForUIControlEvent(.editingDidEndOnExit);
}
@@ -80,7 +80,7 @@ public extension MutableCollection where Index == Int {
Returns a random element from the collection.
- returns: A random element from the collection.
*/
public func random() -> Iterator.Element {
func random() -> Iterator.Element {
let index = Int(arc4random_uniform(UInt32(count)))
return self[index]
}
+11 -11
View File
@@ -4,7 +4,7 @@ import CoreGraphics
public extension Int {
/// Returns a random Int point number between 0 and Int.max.
public static var random: Int {
static var random: Int {
return Int.random(n: Int.max)
}
@@ -12,7 +12,7 @@ public extension Int {
///
/// - Parameter n: Interval max
/// - Returns: Returns a random Int point number between 0 and n max
public static func random(n: Int) -> Int {
static func random(n: Int) -> Int {
return Int(arc4random_uniform(UInt32(n)))
}
@@ -22,7 +22,7 @@ public extension Int {
/// - min: Interval minimun
/// - max: Interval max
/// - Returns: Returns a random Int point number between 0 and n max
public static func random(min: Int, max: Int) -> Int {
static func random(min: Int, max: Int) -> Int {
return Int.random(n: max - min + 1) + min
}
}
@@ -32,7 +32,7 @@ public extension Int {
public extension Double {
/// Returns a random floating point number between 0.0 and 1.0, inclusive.
public static var random: Double {
static var random: Double {
return Double(arc4random()) / 0xFFFFFFFF
}
@@ -40,7 +40,7 @@ public extension Double {
///
/// - Parameter n: Interval max
/// - Returns: Returns a random double point number between 0 and n max
public static func random(min: Double, max: Double) -> Double {
static func random(min: Double, max: Double) -> Double {
return Double.random * (max - min) + min
}
}
@@ -50,15 +50,15 @@ public extension Double {
public extension Float {
/// Returns a random floating point number between 0.0 and 1.0, inclusive.
public static var random: Float {
return Float(arc4random()) / 0xFFFFFFFF
static var random: Float {
return Float(arc4random()) / 4294967296
}
/// Random float between 0 and n-1.
///
/// - Parameter n: Interval max
/// - Returns: Returns a random float point number between 0 and n max
public static func random(min: Float, max: Float) -> Float {
static func random(min: Float, max: Float) -> Float {
return Float.random * (max - min) + min
}
}
@@ -68,12 +68,12 @@ public extension Float {
public extension CGFloat {
/// Randomly returns either 1.0 or -1.0.
public static var randomSign: CGFloat {
static var randomSign: CGFloat {
return (arc4random_uniform(2) == 0) ? 1.0 : -1.0
}
/// Returns a random floating point number between 0.0 and 1.0, inclusive.
public static var random: CGFloat {
static var random: CGFloat {
return CGFloat(Float.random)
}
@@ -81,7 +81,7 @@ public extension CGFloat {
///
/// - Parameter n: Interval max
/// - Returns: Returns a random CGFloat point number between 0 and n max
public static func random(min: CGFloat, max: CGFloat) -> CGFloat {
static func random(min: CGFloat, max: CGFloat) -> CGFloat {
return CGFloat.random * (max - min) + min
}
}
+2 -3
View File
@@ -5,9 +5,8 @@ public let none = None()
public struct None {}
extension None: Hashable {
public var hashValue: Int {
return 0
}
public func hash(into hasher: inout Hasher) { }
}
public func == (lhs: None, rhs: None) -> Bool { return true }
@@ -0,0 +1,42 @@
import UIKit
public class PaginationFlowLayout: UICollectionViewFlowLayout {
override public func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint {
guard let collectionView = collectionView,
let layoutAttributes: Array = layoutAttributesForElements(in: collectionView.bounds),
layoutAttributes.count != 0 else {
return proposedContentOffset
}
var firstAttribute: UICollectionViewLayoutAttributes = layoutAttributes[0]
for attribute: UICollectionViewLayoutAttributes in layoutAttributes {
guard attribute.representedElementCategory == .cell else { continue }
switch scrollDirection {
case .horizontal:
if((velocity.x > 0.0 && attribute.center.x > firstAttribute.center.x) ||
(velocity.x <= 0.0 && attribute.center.x < firstAttribute.center.x)) {
firstAttribute = attribute;
}
case .vertical:
if((velocity.y > 0.0 && attribute.center.y > firstAttribute.center.y) ||
(velocity.y <= 0.0 && attribute.center.y < firstAttribute.center.y)) {
firstAttribute = attribute;
}
@unknown default:
fatalError()
}
}
switch scrollDirection {
case .horizontal:
return CGPoint(x: firstAttribute.center.x - collectionView.bounds.size.width * 0.5, y: proposedContentOffset.y)
case .vertical:
return CGPoint(x: proposedContentOffset.x, y: firstAttribute.center.y - collectionView.bounds.size.height * 0.5)
@unknown default:
fatalError()
}
}
}
@@ -0,0 +1,50 @@
import Foundation
import UIKit
final class SnappingFlowLayout: UICollectionViewFlowLayout {
override func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint {
guard let collectionView = collectionView else {
return super.targetContentOffset(forProposedContentOffset: proposedContentOffset, withScrollingVelocity: velocity)
}
var offsetAdjustment = CGFloat.greatestFiniteMagnitude
switch scrollDirection {
case .horizontal:
let horizontalOffset = proposedContentOffset.x + collectionView.contentInset.left
let targetRect = CGRect(x: proposedContentOffset.x, y: 0, width: collectionView.bounds.size.width, height: collectionView.bounds.size.height)
let layoutAttributesArray = super.layoutAttributesForElements(in: targetRect)
layoutAttributesArray?.forEach { layoutAttributes in
let itemOffset = layoutAttributes.frame.origin.x
if fabsf(Float(itemOffset - horizontalOffset)) < fabsf(Float(offsetAdjustment)) {
offsetAdjustment = itemOffset - horizontalOffset
}
}
let result = CGPoint(x: proposedContentOffset.x + offsetAdjustment, y: proposedContentOffset.y)
return result
case .vertical:
let verticalOffset = proposedContentOffset.y + collectionView.contentInset.top
let targetRect = CGRect(x: 0, y: proposedContentOffset.y, width: collectionView.bounds.size.width, height: collectionView.bounds.size.height)
let layoutAttributesArray = super.layoutAttributesForElements(in: targetRect)
layoutAttributesArray?.forEach { layoutAttributes in
let itemOffset = layoutAttributes.frame.origin.y
if fabsf(Float(itemOffset - verticalOffset)) < fabsf(Float(offsetAdjustment)) {
offsetAdjustment = itemOffset - verticalOffset
}
}
let result = CGPoint(x: proposedContentOffset.x, y: proposedContentOffset.y + offsetAdjustment)
return result
@unknown default:
fatalError()
}
}
}
@@ -0,0 +1,210 @@
import Foundation
import UIKit
public final class VerticalAlignmentLayout: UICollectionViewFlowLayout {
// KVO
fileprivate struct KVO {
var keyPath: String
var options: NSKeyValueObservingOptions
var context: Int
static var contentOffset = KVO(
keyPath: #keyPath(UICollectionViewFlowLayout.collectionView.contentOffset),
options: .new,
context: 0
)
static var bounds = KVO(
keyPath: #keyPath(UICollectionViewFlowLayout.collectionView.bounds),
options: .new,
context: 1
)
}
public enum Alignment {
case top
case center
case bottom
}
// Properties
public var currentPage: Int?
public var currentPageDidChange: (Int) -> Void = { _ in }
let alignment: Alignment
var collectionHeight: CGFloat {
guard let collection = collectionView else { return 0 }
return collection.bounds.size.height - collection.contentInset.top - collection.contentInset.bottom
}
deinit {
removeObserver(self, forKeyPath: KVO.bounds.keyPath, context: &KVO.bounds.context)
}
public init(itemSize: CGSize, spacing: CGFloat, alignment: Alignment) {
self.alignment = alignment
super.init()
self.itemSize = itemSize
self.scrollDirection = .vertical
self.minimumLineSpacing = spacing
addObserver(self,
forKeyPath: KVO.bounds.keyPath,
options: KVO.bounds.options,
context: &KVO.bounds.context)
addObserver(self,
forKeyPath: KVO.contentOffset.keyPath,
options: KVO.contentOffset.options,
context: &KVO.contentOffset.context)
}
public required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") }
}
// MARK: override
extension VerticalAlignmentLayout {
public override func prepare() {
switch alignment {
case .top:
sectionInset.top = 0
sectionInset.bottom = collectionHeight - itemSize.height
case .center:
let inset = (collectionHeight - itemSize.height) / 2
sectionInset.top = inset
sectionInset.bottom = inset
case .bottom:
sectionInset.top = collectionHeight - itemSize.height
sectionInset.bottom = 0
}
super.prepare()
}
public override func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint {
guard let collectionView = self.collectionView else {
return proposedContentOffset
}
let proposedRect = CGRect(x: proposedContentOffset.x, y: 0, width: collectionView.bounds.width, height: collectionView.bounds.height)
guard let layoutAttributes = self.layoutAttributesForElements(in: proposedRect) else {
return proposedContentOffset
}
var candidateAttributes: UICollectionViewLayoutAttributes?
let proposedContentOffsetY: CGFloat
switch alignment {
case .top:
proposedContentOffsetY = proposedContentOffset.y + itemSize.height / 2
case .center:
proposedContentOffsetY = proposedContentOffset.y + collectionView.bounds.height / 2
case .bottom:
proposedContentOffsetY = proposedContentOffset.y + collectionView.bounds.height
}
for attributes in layoutAttributes {
guard attributes.representedElementCategory == .cell else { continue }
if candidateAttributes == nil {
candidateAttributes = attributes
continue
}
let attributePosition: CGFloat
let candidatePosition: CGFloat
switch alignment {
case .top:
attributePosition = attributes.frame.minY
candidatePosition = candidateAttributes!.frame.minY
case .center:
attributePosition = attributes.center.y
candidatePosition = candidateAttributes!.center.y
case .bottom:
attributePosition = attributes.frame.maxY
candidatePosition = candidateAttributes!.frame.maxY
}
if abs(attributePosition - proposedContentOffsetY) < abs(candidatePosition - proposedContentOffsetY) {
candidateAttributes = attributes
}
}
guard let aCandidateAttributes = candidateAttributes else {
return proposedContentOffset
}
var newOffsetY: CGFloat
switch alignment {
case .top:
newOffsetY = aCandidateAttributes.frame.minY - collectionView.contentInset.top
case .center:
newOffsetY = aCandidateAttributes.center.y - collectionView.bounds.size.height / 2
case .bottom:
newOffsetY = aCandidateAttributes.frame.minY - collectionView.bounds.size.height + itemSize.width
}
let offset = newOffsetY - collectionView.contentOffset.y
if (velocity.y < 0 && offset > 0) || (velocity.y > 0 && offset < 0) {
let pageHeight = itemSize.height + minimumLineSpacing
newOffsetY += velocity.y > 0 ? pageHeight : -pageHeight
}
return CGPoint(x: proposedContentOffset.x, y: newOffsetY)
}
public override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
return true
}
public override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
switch context {
case (&KVO.bounds.context)?:
// TODO: Current layout makes hack for first item to appear in the center.
// (method configure Insets). Settings insets shouldn't be done in invalidateLayout.
// Layout should handle appear state on it's own.
let oldValue = change?[.oldKey] as? NSValue
let newValue = change?[.newKey] as? NSValue
if oldValue?.cgRectValue != newValue?.cgRectValue {
invalidateLayout()
}
case (&KVO.contentOffset.context)?:
guard let collectionView = collectionView, collectionView.frame.size != CGSize.zero else {
return
}
let visibleRect = CGRect(origin: collectionView.contentOffset, size: collectionView.frame.size)
let layoutAttributes = layoutAttributesForElements(in: visibleRect)
let center: CGFloat
switch alignment {
case .top:
let middle = collectionView.contentInset.top + itemSize.height / 2
center = collectionView.contentOffset.y + middle
case .center:
let middle = collectionView.frame.height / 2
center = collectionView.contentOffset.y + middle
case .bottom:
let middle = collectionView.frame.height - collectionView.contentInset.bottom - itemSize.height / 2
center = collectionView.contentOffset.y + middle
}
let closestAttribute = layoutAttributes?.sorted {
abs($0.center.y - center) < abs($1.center.y - center)
}.first
if let closestAttribute = closestAttribute, currentPage != closestAttribute.indexPath.row {
currentPage = closestAttribute.indexPath.row
if let currentPage = currentPage {
currentPageDidChange(currentPage)
}
}
default:
super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
}
}
}
@@ -3,7 +3,7 @@ import UIKit
public extension CALayer {
@discardableResult
public func add(to superlayer: CALayer) -> Self {
func add(to superlayer: CALayer) -> Self {
superlayer.addSublayer(self)
return self
}
@@ -11,7 +11,7 @@ public extension CALayer {
public extension CATransaction {
public static func withoutActions(_ block: () -> Void) {
static func withoutActions(_ block: () -> Void) {
begin()
setDisableActions(true)
block()
@@ -1,11 +1,3 @@
//
// CoreGraphics+ext.swift
// Vendefy
//
// Created by Dmitriy Kalachev on 4/8/18.
// Copyright © 2018 Ramotion. All rights reserved.
//
import UIKit
public extension CGSize {
@@ -14,13 +6,13 @@ public extension CGSize {
self.init(width: value, height: value)
}
init(_ width: CGFloat, _ height: CGFloat) {
self.init(width: width, height: height)
}
var asRect: CGRect {
return CGRect(size: self)
}
init(_ width: CGFloat, _ height: CGFloat) {
self.init(width: width, height: height)
}
func centered(in rect: CGRect) -> CGRect {
var result = self.asRect
@@ -29,14 +21,29 @@ public extension CGSize {
return result
}
func centered(in size: CGSize) -> CGRect {
return centered(in: CGRect(origin: .zero, size: size))
}
var asPixelsForMainScreen: CGSize {
return self * UIScreen.main.scale
}
var center: CGPoint {
return CGPoint(x: width / 2, y: height / 2)
}
static var greatestFiniteMagnitude: CGSize {
return CGSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude)
}
var area: CGFloat {
return width * height
}
}
public extension UIEdgeInsets {
public init(value: CGFloat) {
init(value: CGFloat) {
self.init(top: value, left: value, bottom: value, right: value)
}
}
@@ -59,6 +66,13 @@ public extension CGRect {
return CGPoint(x: midX, y: midY)
}
func insetBy(insets: UIEdgeInsets) -> CGRect {
let x = origin.x + insets.left
let y = origin.y + insets.top
let w = size.width - insets.left - insets.right
let h = size.height - insets.top - insets.bottom
return CGRect(x: x, y: y, width: w, height: h)
}
}
public extension CGAffineTransform {
@@ -66,7 +80,6 @@ public extension CGAffineTransform {
init(scale: CGFloat) {
self.init(scaleX: scale, y: scale)
}
}
public func + (lhs: CGPoint, rhs: CGPoint) -> CGPoint {
@@ -7,7 +7,7 @@ public extension UIButton {
/// - bottom: title below button image
/// - left: title to the left of button image
/// - right: title to the right of button image
public enum Position: Int {
enum Position: Int {
case top, bottom, left, right
}
@@ -20,7 +20,7 @@ public extension UIButton {
/// - titlePosition: UIViewContentModeTop, UIViewContentModeBottom, UIViewContentModeLeft or UIViewContentModeRight
/// - additionalSpacing: Spacing between image and title
/// - state: State to apply this behaviour
public func set(image: UIImage?, title: String, titlePosition: Position, spacing: CGFloat, state: UIControl.State){
func set(image: UIImage?, title: String, titlePosition: Position, spacing: CGFloat, state: UIControl.State){
imageView?.contentMode = .center
setImage(image, for: state)
setTitle(title, for: state)
@@ -39,7 +39,7 @@ public extension UIButton {
/// - titlePosition: UIViewContentModeTop, UIViewContentModeBottom, UIViewContentModeLeft or UIViewContentModeRight
/// - additionalSpacing: Spacing between image and title
/// - state: State to apply this behaviour
public func set(image: UIImage?, attributedTitle title: NSAttributedString, titlePosition: Position, spacing: CGFloat, state: UIControl.State){
func set(image: UIImage?, attributedTitle title: NSAttributedString, titlePosition: Position, spacing: CGFloat, state: UIControl.State){
imageView?.contentMode = .center
setImage(image, for: state)
@@ -15,7 +15,7 @@ public extension UINavigationController {
}
func replace(_ vc: UIViewController, with replacementVC: UIViewController, animated: Bool = true) {
guard let index = self.viewControllers.index(of: vc)
guard let index = self.viewControllers.firstIndex(of: vc)
else { return }
var viewControllers = self.viewControllers
@@ -3,31 +3,31 @@ import UIKit
public extension UIView {
@discardableResult
public func add(to superview: UIView) -> Self {
func add(to superview: UIView) -> Self {
superview.addSubview(self)
return self
}
@discardableResult
public func insert(to superview: UIView, at index: Int) -> Self {
func insert(to superview: UIView, at index: Int) -> Self {
superview.insertSubview(self, at: index)
return self
}
@discardableResult
public func insert(to superview: UIView, above view: UIView) -> Self {
func insert(to superview: UIView, above view: UIView) -> Self {
superview.insertSubview(self, aboveSubview: view)
return self
}
@discardableResult
public func insert(to superview: UIView, below view: UIView) -> Self {
func insert(to superview: UIView, below view: UIView) -> Self {
superview.insertSubview(self, belowSubview: view)
return self
}
@discardableResult
public func add(to stackview: UIStackView) -> Self {
func add(to stackview: UIStackView) -> Self {
stackview.addArrangedSubview(self)
return self
}
@@ -182,7 +182,7 @@ public extension UIView {
static var viewExtension = "viewExtensionKeyboardVisibilityController"
}
public var keyboardVisibilityController: InputVisibilityController? {
var keyboardVisibilityController: InputVisibilityController? {
get {
return objc_getAssociatedObject(self, &KeyboardAssociatedKey.viewExtension) as? InputVisibilityController ?? nil
}
@@ -192,7 +192,7 @@ public extension UIView {
}
@discardableResult
public func addInputVisibilityController() -> InputVisibilityController {
func addInputVisibilityController() -> InputVisibilityController {
var keyboardController = self.keyboardVisibilityController
if keyboardController == nil {
@@ -211,14 +211,14 @@ public extension UIView {
return keyboardVisibilityController!
}
public func removeKeyboardVisibilityController() {
func removeKeyboardVisibilityController() {
keyboardVisibilityController = nil
}
}
public extension UIView {
public func findFirstResponder() -> UIView? {
func findFirstResponder() -> UIView? {
if isFirstResponder {
return self
}
+3 -3
View File
@@ -4,7 +4,7 @@ import UIKit
public extension UIView {
public func snapshotImage(opaque: Bool = true, scale: CGFloat = UIScreen.main.scale * 2, afterScreenUpdates: Bool = false) -> UIImage? {
func snapshotImage(opaque: Bool = true, scale: CGFloat = UIScreen.main.scale * 2, afterScreenUpdates: Bool = false) -> UIImage? {
UIGraphicsBeginImageContextWithOptions(bounds.size, opaque, scale)
drawHierarchy(in: bounds, afterScreenUpdates: afterScreenUpdates)
let snapshotImage = UIGraphicsGetImageFromCurrentImageContext()
@@ -12,7 +12,7 @@ public extension UIView {
return snapshotImage
}
public func snapshotView(opaque: Bool = true, scale: CGFloat = UIScreen.main.scale * 2, afterScreenUpdates: Bool = false) -> UIView? {
func snapshotView(opaque: Bool = true, scale: CGFloat = UIScreen.main.scale * 2, afterScreenUpdates: Bool = false) -> UIView? {
if let snapshotImage = snapshotImage(opaque: opaque, scale: scale, afterScreenUpdates: afterScreenUpdates) {
return UIImageView(image: snapshotImage)
} else {
@@ -20,7 +20,7 @@ public extension UIView {
}
}
public func snapshotLayer(opaque: Bool = true, scale: CGFloat = UIScreen.main.scale * 2) -> UIImage? {
func snapshotLayer(opaque: Bool = true, scale: CGFloat = UIScreen.main.scale * 2) -> UIImage? {
UIGraphicsBeginImageContextWithOptions(bounds.size, opaque, scale)
guard let context = UIGraphicsGetCurrentContext() else { return nil }
+12 -2
View File
@@ -2,10 +2,14 @@ import Foundation
import UIKit
public protocol Togglable: class {
var isOn: Bool { get }
func selectedToggle(select: Bool)
}
extension UIControl: Togglable {
public var isOn: Bool { return isSelected }
@objc public func selectedToggle(select: Bool) {
isSelected = select
}
@@ -22,9 +26,13 @@ extension UISwitch {
public struct Toggler {
var togglers = [Togglable]()
public var index: Int {
return togglers.firstIndex(where: { $0.isOn }) ?? 0
}
public init(default index: Int = 0, togglers: [Togglable]) {
self.togglers = togglers
toggleControl(toggle: togglers[index], togglers: togglers)
onAt(index: index)
}
public func on(toggle: Togglable) {
@@ -32,7 +40,9 @@ public struct Toggler {
}
public func onAt(index: Int) {
toggleControl(toggle: togglers[index], togglers: togglers)
if let toggler = togglers.at(index) {
toggleControl(toggle: toggler, togglers: togglers)
}
}
public mutating func add(toggle: Togglable) {
+14 -1
View File
@@ -109,7 +109,20 @@ import UIKit
setNeedsDisplay()
}
}
public override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required public init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
private func setup() {
backgroundColor = .clear
}
// MARK: - UIView
override open func draw(_ rect: CGRect) {
+4 -4
View File
@@ -2,19 +2,19 @@ import Foundation
public extension Bundle {
public var appName: String {
var appName: String {
return infoDictionary?["CFBundleName"] as! String
}
public var bundleId: String {
var bundleId: String {
return bundleIdentifier!
}
public var versionNumber: String {
var versionNumber: String {
return infoDictionary?["CFBundleShortVersionString"] as! String
}
public var buildNumber: String {
var buildNumber: String {
return infoDictionary?["CFBundleVersion"] as! String
}
}
@@ -0,0 +1,515 @@
import Foundation
extension NSAttributedString {
/**
Returns a new mutable string with characters from a given character set removed.
See http://panupan.com/2012/06/04/trim-leading-and-trailing-whitespaces-from-nsmutableattributedstring/
- Parameters:
- charSet: The character set with which to remove characters.
- returns: A new string with the matching characters removed.
*/
public func trimmingCharacters(in set: CharacterSet) -> NSAttributedString {
let modString = NSMutableAttributedString(attributedString: self)
modString.trimCharacters(in: set)
return NSAttributedString(attributedString: modString)
}
}
extension NSMutableAttributedString {
/**
Modifies this instance of the string to remove characters from a given character set from
the beginning and end of the string.
See http://panupan.com/2012/06/04/trim-leading-and-trailing-whitespaces-from-nsmutableattributedstring/
- Parameters:
- charSet: The character set with which to remove characters.
*/
public func trimCharacters(in set: CharacterSet) {
var range = (string as NSString).rangeOfCharacter(from: set)
// Trim leading characters from character set.
while range.length != 0 && range.location == 0 {
replaceCharacters(in: range, with: "")
range = (string as NSString).rangeOfCharacter(from: set)
}
// Trim trailing characters from character set.
range = (string as NSString).rangeOfCharacter(from: set, options: .backwards)
while range.length != 0 && NSMaxRange(range) == length {
replaceCharacters(in: range, with: "")
range = (string as NSString).rangeOfCharacter(from: set, options: .backwards)
}
}
}
extension NSMutableAttributedString {
private var range: NSRange {
return NSRange(location: 0, length: length)
}
private var paragraphStyle: NSMutableParagraphStyle {
let style = attributes(at: 0, effectiveRange: nil)[.paragraphStyle] as? NSMutableParagraphStyle
return style ?? NSMutableParagraphStyle()
}
}
// MARK: - Font
extension NSMutableAttributedString {
/**
Applies a font to the entire string.
- parameter font: The font.
*/
@discardableResult
public func font(_ font: UIFont) -> Self {
if length > 0 {
addAttribute(.font, value: font, range: range)
}
return self
}
/**
Applies a font to the entire string.
- parameter name: The font name.
- parameter size: The font size.
Note: If the specified font name cannot be loaded, this method will fallback to the system font at the specified size.
*/
@discardableResult
public func font(name: String, size: CGFloat) -> Self {
if length > 0 {
addAttribute(.font, value: UIFont(name: name, size: size) ?? .systemFont(ofSize: size), range: range)
}
return self
}
}
// MARK: - Paragraph style
extension NSMutableAttributedString {
/**
Applies a text alignment to the entire string.
- parameter alignment: The text alignment.
*/
@discardableResult
public func alignment(_ alignment: NSTextAlignment) -> Self {
if length > 0 {
let paragraphStyle = self.paragraphStyle
paragraphStyle.alignment = alignment
addAttribute(.paragraphStyle, value: paragraphStyle, range: range)
}
return self
}
/**
Applies line spacing to the entire string.
- parameter lineSpacing: The line spacing amount.
*/
@discardableResult
public func lineSpacing(_ lineSpacing: CGFloat) -> Self {
if length > 0 {
let paragraphStyle = self.paragraphStyle
paragraphStyle.lineSpacing = lineSpacing
addAttribute(.paragraphStyle, value: paragraphStyle, range: range)
}
return self
}
/**
Applies paragraph spacing to the entire string.
- parameter paragraphSpacing: The paragraph spacing amount.
*/
@discardableResult
public func paragraphSpacing(_ paragraphSpacing: CGFloat) -> Self {
if length > 0 {
let paragraphStyle = self.paragraphStyle
paragraphStyle.paragraphSpacing = paragraphSpacing
addAttribute(.paragraphStyle, value: paragraphStyle, range: range)
}
return self
}
/**
Applies a line break mode to the entire string.
- parameter mode: The line break mode.
*/
@discardableResult
public func lineBreak(_ mode: NSLineBreakMode) -> Self {
if length > 0 {
let paragraphStyle = self.paragraphStyle
paragraphStyle.lineBreakMode = mode
addAttribute(.paragraphStyle, value: paragraphStyle, range: range)
}
return self
}
/**
Applies a line height multiplier to the entire string.
- parameter multiple: The line height multiplier.
*/
@discardableResult
public func lineHeight(multiple: CGFloat) -> Self {
if length > 0 {
let paragraphStyle = self.paragraphStyle
paragraphStyle.lineHeightMultiple = multiple
addAttribute(.paragraphStyle, value: paragraphStyle, range: range)
}
return self
}
/**
Applies a first line head indent to the string.
- parameter indent: The first line head indent amount.
*/
@discardableResult
public func firstLineHeadIndent(_ indent: CGFloat) -> Self {
if length > 0 {
let paragraphStyle = self.paragraphStyle
paragraphStyle.firstLineHeadIndent = indent
addAttribute(.paragraphStyle, value: paragraphStyle, range: range)
}
return self
}
/**
Applies a head indent to the string.
- parameter indent: The head indent amount.
*/
@discardableResult
public func headIndent(_ indent: CGFloat) -> Self {
if length > 0 {
let paragraphStyle = self.paragraphStyle
paragraphStyle.headIndent = indent
addAttribute(.paragraphStyle, value: paragraphStyle, range: range)
}
return self
}
/**
Applies a tail indent to the string.
- parameter indent: The tail indent amount.
*/
@discardableResult
public func tailIndent(_ indent: CGFloat) -> Self {
if length > 0 {
let paragraphStyle = self.paragraphStyle
paragraphStyle.tailIndent = indent
addAttribute(.paragraphStyle, value: paragraphStyle, range: range)
}
return self
}
/**
Applies a minimum line height to the entire string.
- parameter height: The minimum line height.
*/
@discardableResult
public func minimumLineHeight(_ height: CGFloat) -> Self {
if length > 0 {
let paragraphStyle = self.paragraphStyle
paragraphStyle.minimumLineHeight = height
addAttribute(.paragraphStyle, value: paragraphStyle, range: range)
}
return self
}
/**
Applies a maximum line height to the entire string.
- parameter height: The maximum line height.
*/
@discardableResult
public func maximumLineHeight(_ height: CGFloat) -> Self {
if length > 0 {
let paragraphStyle = self.paragraphStyle
paragraphStyle.maximumLineHeight = height
addAttribute(.paragraphStyle, value: paragraphStyle, range: range)
}
return self
}
/**
Applies a base writing direction to the entire string.
- parameter direction: The base writing direction.
*/
@discardableResult
public func baseWritingDirection(_ direction: NSWritingDirection) -> Self {
if length > 0 {
let paragraphStyle = self.paragraphStyle
paragraphStyle.baseWritingDirection = direction
addAttribute(.paragraphStyle, value: paragraphStyle, range: range)
}
return self
}
/**
Applies a paragraph spacing before amount to the string.
- parameter spacing: The distance between the paragraphs top and the beginning of its text content.
*/
@discardableResult
public func paragraphSpacingBefore(_ spacing: CGFloat) -> Self {
if length > 0 {
let paragraphStyle = self.paragraphStyle
paragraphStyle.paragraphSpacingBefore = spacing
addAttribute(.paragraphStyle, value: paragraphStyle, range: range)
}
return self
}
}
// MARK: - Foreground color
extension NSMutableAttributedString {
/**
Applies the given color over the entire string, as the foreground color.
- parameter color: The color to apply.
*/
@discardableResult @nonobjc
public func color(_ color: UIColor, alpha: CGFloat = 1) -> Self {
if length > 0 {
addAttribute(.foregroundColor, value: color.withAlphaComponent(alpha), range: range)
}
return self
}
/**
Applies the given color over the entire string, as the foreground color.
- parameter color: The color to apply.
*/
@discardableResult
public func color(red: CGFloat, green: CGFloat, blue: CGFloat, alpha: CGFloat = 1) -> Self {
if length > 0 {
addAttribute(.foregroundColor, value: UIColor(red: red, green: green, blue: blue, alpha: alpha), range: range)
}
return self
}
/**
Applies the given color over the entire string, as the foreground color.
- parameter color: The color to apply.
*/
@discardableResult
public func color(white: CGFloat, alpha: CGFloat = 1) -> Self {
if length > 0 {
addAttribute(.foregroundColor, value: UIColor(white: white, alpha: alpha), range: range)
}
return self
}
}
// MARK: - Underline, kern, strikethrough, stroke, shadow, text effect
extension NSMutableAttributedString {
/**
Applies a single underline under the entire string.
- parameter style: The `NSUnderlineStyle` to apply. Defaults to `.styleSingle`.
*/
@discardableResult
public func underline(style: NSUnderlineStyle = .single, color: UIColor? = nil) -> Self {
if length > 0 {
addAttribute(.underlineStyle, value: style.rawValue, range: range)
if let color = color {
addAttribute(.underlineColor, value: color, range: range)
}
}
return self
}
/**
Applies a kern (spacing) value to the entire string.
- parameter value: The space between each character in the string.
*/
@discardableResult
public func kern(_ value: CGFloat) -> Self {
if length > 0 {
addAttribute(.kern, value: value, range: range)
}
return self
}
/**
Applies a strikethrough to the entire string.
- parameter style: The `NSUnderlineStyle` to apply. Defaults to `.styleSingle`.
- parameter color: The underline color. Defaults to the color of the text.
*/
@discardableResult
public func strikethrough(style: NSUnderlineStyle = .single, color: UIColor? = nil) -> Self {
if length > 0 {
addAttribute(.strikethroughStyle, value: style.rawValue, range: range)
if let color = color {
addAttribute(.strikethroughColor, value: color, range: range)
}
}
return self
}
/**
Applies a stroke to the entire string.
- parameter color: The stroke color.
- parameter width: The stroke width.
*/
@discardableResult
public func stroke(color: UIColor, width: CGFloat) -> Self {
if length > 0 {
addAttributes([
.strokeColor : color,
.strokeWidth : width
], range: range)
}
return self
}
/**
Applies a shadow to the entire string.
- parameter color: The shadow color.
- parameter radius: The shadow blur radius.
- parameter offset: The shadow offset.
*/
@discardableResult
public func shadow(color: UIColor, radius: CGFloat, offset: CGSize) -> Self {
if length > 0 {
let shadow = NSShadow()
shadow.shadowColor = color
shadow.shadowBlurRadius = radius
shadow.shadowOffset = offset
addAttribute(.shadow, value: shadow, range: range)
}
return self
}
}
// MARK: - Background color
extension NSMutableAttributedString {
/**
Applies a background color to the entire string.
- parameter color: The color to apply.
*/
@discardableResult @nonobjc
public func backgroundColor(_ color: UIColor, alpha: CGFloat = 1) -> Self {
if length > 0 {
addAttribute(.backgroundColor, value: color.withAlphaComponent(alpha), range: range)
}
return self
}
/**
Applies a background color to the entire string.
- parameter red: The red color component.
- parameter green: The green color component.
- parameter blue: The blue color component.
- parameter alpha: The alpha component.
*/
@discardableResult
public func backgroundColor(red: CGFloat, green: CGFloat, blue: CGFloat, alpha: CGFloat = 1) -> Self {
if length > 0 {
addAttribute(.backgroundColor, value: UIColor(red: red, green: green, blue: blue, alpha: alpha), range: range)
}
return self
}
/**
Applies a background color to the entire string.
- parameter white: The white color component.
- parameter alpha: The alpha component.
*/
@discardableResult
public func backgroundColor(white: CGFloat, alpha: CGFloat = 1) -> Self {
if length > 0 {
addAttribute(.backgroundColor, value: UIColor(white: white, alpha: alpha), range: range)
}
return self
}
}
extension NSMutableAttributedString {
/**
Applies a baseline offset to the entire string.
- parameter offset: The offset value.
*/
@discardableResult
public func baselineOffset(_ offset: Float) -> Self {
if length > 0 {
addAttribute(.baselineOffset, value: NSNumber(value: offset), range: range)
}
return self
}
}
public func +(lhs: NSMutableAttributedString, rhs: NSAttributedString) -> NSMutableAttributedString {
let lhs = NSMutableAttributedString(attributedString: lhs)
lhs.append(rhs)
return lhs
}
public func +=(lhs: NSMutableAttributedString, rhs: NSAttributedString) {
lhs.append(rhs)
}
+2 -39
View File
@@ -80,7 +80,7 @@ public class Reachability {
public var whenReachable: NetworkReachable?
public var whenUnreachable: NetworkUnreachable?
@available(*, deprecated: 4.0, renamed: "allowsCellularConnection")
@available(*, deprecated, renamed: "allowsCellularConnection")
public let reachableOnWWAN: Bool = true
/// Set to `false` to force Reachability.connection to .none when on cellular connection (default value `true`)
@@ -89,7 +89,7 @@ public class Reachability {
// The notification center on which "reachability changed" events are being posted
public var notificationCenter: NotificationCenter = NotificationCenter.default
@available(*, deprecated: 4.0, renamed: "connection.description")
@available(*, deprecated, renamed: "connection.description")
public var currentReachabilityString: String {
return "\(connection)"
}
@@ -204,43 +204,6 @@ public extension Reachability {
SCNetworkReachabilitySetDispatchQueue(reachabilityRef, nil)
}
// MARK: - *** Connection test methods ***
@available(*, deprecated: 4.0, message: "Please use `connection != .none`")
var isReachable: Bool {
guard isReachableFlagSet else { return false }
if isConnectionRequiredAndTransientFlagSet {
return false
}
if isRunningOnDevice {
if isOnWWANFlagSet && !reachableOnWWAN {
// We don't want to connect when on cellular connection
return false
}
}
return true
}
@available(*, deprecated: 4.0, message: "Please use `connection == .cellular`")
var isReachableViaWWAN: Bool {
// Check we're not on the simulator, we're REACHABLE and check we're on WWAN
return isRunningOnDevice && isReachableFlagSet && isOnWWANFlagSet
}
@available(*, deprecated: 4.0, message: "Please use `connection == .wifi`")
var isReachableViaWiFi: Bool {
// Check we're reachable
guard isReachableFlagSet else { return false }
// If reachable we're reachable, but not on an iOS device (i.e. simulator), we must be on WiFi
guard isRunningOnDevice else { return true }
// Check we're NOT on WWAN
return !isOnWWANFlagSet
}
var description: String {
let W = isRunningOnDevice ? (isOnWWANFlagSet ? "W" : "-") : "X"
let R = isReachableFlagSet ? "R" : "-"
@@ -0,0 +1,330 @@
import Foundation
extension String {
/// Converts self to an unsigned byte array.
public var bytes: [UInt8] {
return utf8.map { $0 }
}
/// Converts self to an NSMutableAttributedString.
public var attributed: NSMutableAttributedString {
return NSMutableAttributedString(string: self)
}
/// Converts self to an NSString.
public var ns: NSString {
return self as NSString
}
/**
Converts string to camel-case.
Examples:
```
"os version".camelCasedString // "osVersion"
"HelloWorld".camelCasedString // "helloWorld"
"someword With Characters".camelCasedString // "somewordWithCharacters"
```
*/
public var camelCased: String {
guard !isEmpty else { return self }
if contains(" ") {
let first = self[0].lowercased()
let cammel = capitalized.replacingOccurrences(of: " ", with: "")
let rest = String(cammel.dropFirst())
return first + rest
} else {
let first = self[0].lowercased()
let rest = String(dropFirst())
return first + rest
}
}
/**
The base64 encoded version of self.
Credit: http://stackoverflow.com/a/29365954
*/
public var base64Encoded: String? {
let utf8str = data(using: .utf8)
return utf8str?.base64EncodedString()
}
/**
The decoded value of a base64 encoded string
Credit: http://stackoverflow.com/a/29365954
*/
public var base64Decoded: String? {
guard let data = Data(base64Encoded: self, options: []) else { return nil }
return String(data: data, encoding: .utf8)
}
/**
Returns true if every character within the string is a numeric character. Empty strings are
considered non-numeric.
*/
public var isNumeric: Bool {
guard !isEmpty else { return false }
return trimmingCharacters(in: .decimalDigits).isEmpty
}
/**
Replaces all occurences of the pattern on self in-place.
Examples:
```
"hello".regexInPlace("[aeiou]", "*") // "h*ll*"
"hello".regexInPlace("([aeiou])", "<$1>") // "h<e>ll<o>"
```
*/
public mutating func formRegex(_ pattern: String, _ replacement: String) {
do {
let expression = try NSRegularExpression(pattern: pattern, options: [])
let range = NSRange(location: 0, length: count)
self = expression.stringByReplacingMatches(in: self, options: [], range: range, withTemplate: replacement)
}
catch { return }
}
/**
Returns a string containing replacements for all pattern matches.
Examples:
```
"hello".regex("[aeiou]", "*") // "h*ll*"
"hello".regex("([aeiou])", "<$1>") // "h<e>ll<o>"
```
*/
public func regex(_ pattern: String, _ replacement: String) -> String {
var replacementString = self
replacementString.formRegex(pattern, replacement)
return replacementString
}
/**
Replaces pattern-matched strings, operated upon by a closure, on self in-place.
- parameter pattern: The pattern to match against.
- parameter matches: The closure in which to handle matched strings.
Example:
```
"hello".regexInPlace(".") {
let s = $0.unicodeScalars
let v = s[s.startIndex].value
return "\(v) "
} // "104 101 108 108 111 "
*/
public mutating func formRegex(_ pattern: String, _ matches: (String) -> String) {
let expression: NSRegularExpression
do {
expression = try NSRegularExpression(pattern: "(\(pattern))", options: [])
}
catch {
print("regex error: \(error)")
return
}
let range = NSMakeRange(0, self.count)
var startOffset = 0
let results = expression.matches(in: self, options: [], range: range)
for result in results {
var endOffset = startOffset
for i in 1..<result.numberOfRanges {
var resultRange = result.range
resultRange.location += startOffset
let startIndex = self.index(self.startIndex, offsetBy: resultRange.location)
let endIndex = self.index(self.startIndex, offsetBy: resultRange.location + resultRange.length)
let replacementRange = startIndex ..< endIndex
let match = expression.replacementString(for: result, in: self, offset: startOffset, template: "$\(i)")
let replacement = matches(match)
self.replaceSubrange(replacementRange, with: replacement)
endOffset += replacement.count - resultRange.length
}
startOffset = endOffset
}
}
/**
Returns a string with pattern-matched strings, operated upon by a closure.
- parameter pattern: The pattern to match against.
- parameter matches: The closure in which to handle matched strings.
- returns: String containing replacements for the matched pattern.
Example:
```
"hello".regex(".") {
let s = $0.unicodeScalars
let v = s[s.startIndex].value
return "\(v) "
} // "104 101 108 108 111 "
*/
public func regex(_ pattern: String, _ matches: (String) -> String) -> String {
var replacementString = self
replacementString.formRegex(pattern, matches)
return replacementString
}
/// Substring at index
public subscript(i: Int) -> String {
let index = safeIndex(offset: i)
let contains = index.flatMap(indices.contains) ?? false
return contains ? String(self[index!]) : ""
}
/// Substring for range
public subscript(r: Range<Int>) -> String {
return self[r.lowerBound ... (r.upperBound - 1)]
}
/// Substring for closed range
public subscript(r: ClosedRange<Int>) -> String {
let startIndex = safeIndex(offset: r.lowerBound)
let endIndex = safeIndex(offset: r.upperBound)
let containsStart = startIndex.flatMap(indices.contains) ?? false
let containsEnd = endIndex.flatMap(indices.contains) ?? false
switch (containsStart, containsEnd) {
case (true, true): return String(self[startIndex! ... endIndex!])
case (true, false): return String(self[startIndex!...])
case (false, true): return String(self[...endIndex!])
case (false, false): return ""
}
}
/// Substring for countable partial range
public subscript(r: CountablePartialRangeFrom<Int>) -> String {
let index = safeIndex(offset: r.lowerBound)
let contains = index.flatMap(indices.contains) ?? false
return contains ? String(self[index!...]) : ""
}
/// Substring for partial range through upper bound
public subscript(r: PartialRangeThrough<Int>) -> String {
let index = safeIndex(offset: r.upperBound)
let contains = index.flatMap(indices.contains) ?? false
return contains ? String(self[...index!]) : ""
}
/// Substring for partial range up to upper bound
public subscript(r: PartialRangeUpTo<Int>) -> String {
let index = safeIndex(offset: r.upperBound)
let contains = index.flatMap(indices.contains) ?? false
return contains ? String(self[..<index!]) : ""
}
/**
Truncates the string to length characters, optionally appending a trailing string. If the string is shorter
than the required length, then this function is a non-op.
- parameter length: The length of string required.
- parameter trailing: An optional addition to the end of the string (increasing "length"), such as ellipsis.
- returns: The truncated string.
Examples:
```
"hello there".truncated(to: 5) // "hello"
"hello there".truncated(to: 5, trailing: "...") // "hello..."
```
*/
public func truncated(to length: Int, trailing: String = "") -> String {
guard !isEmpty && count > length else { return self }
return self[..<length] + trailing
}
public mutating func truncate(to length: Int, trailing: String = "") {
self = truncated(to: length, trailing: trailing)
}
/**
A bridge for invoking `String.localizedStandardContainsString()`, which is available in iOS 9 and later. If you need to
support iOS versions prior to iOS 9, use `compatibleStandardContainsString()` as a means to bridge functionality.
If you can support iOS 9 or greater only, use `localizedStandardContainsString()` directly.
From Apple's Swift 2.1 documentation:
`localizedStandardContainsString()` is the most appropriate method for doing user-level string searches, similar to how searches are done generally in the system. The search is locale-aware, case and diacritic insensitive. The exact list of search options applied may change over time.
- parameter string: The string to determine if is contained by self.
- returns: Returns true if self contains string, taking the current locale into account.
*/
public func compatibleStandardContains(_ string: String) -> Bool {
if #available(iOS 9.0, *) {
return localizedStandardContains(string)
}
return range(of: string, options: [.caseInsensitive, .diacriticInsensitive], locale: .current) != nil
}
/**
Convert an NSRange to a Range. There is still a mismatch between the regular expression libraries
and NSString/String. This makes it easier to convert between the two. Using this allows complex
strings (including emoji, regonial indicattors, etc.) to be manipulated without having to resort
to NSString instances.
Note that it may not always be possible to convert from an NSRange as they are not exactly the same.
Taken from:
http://stackoverflow.com/questions/25138339/nsrange-to-rangestring-index
- parameter nsRange: The NSRange instance to covert to a Range.
- returns: The Range, if it was possible to convert. Otherwise nil.
*/
public func range(from nsRange: NSRange) -> Range<String.Index>? {
return Range(nsRange, in: self)
}
/**
Convert a Range to an NSRange. There is still a mismatch between the regular expression libraries
and NSString/String. This makes it easier to convert between the two. Using this allows complex
strings (including emoji, regonial indicators, etc.) to be manipulated without having to resort
to NSString instances.
Taken from:
http://stackoverflow.com/questions/25138339/nsrange-to-rangestring-index
- parameter range: The Range instance to conver to an NSRange.
- returns: The NSRange converted from the input. This will always succeed.
*/
public func nsRange(from range: Range<String.Index>) -> NSRange {
return NSRange(range, in: self)
}
}
private extension String {
func safeIndex(offset: Int) -> String.Index? {
return index(startIndex, offsetBy: offset.limited(0, .max), limitedBy: endIndex)
}
}
@@ -0,0 +1,460 @@
import Foundation
import UIKit
extension UIColor {
/**
Returns a UIColor from the given hexidecimal integer.
- parameter hex: The hex component of the color object, specified as a value from 0x000000 to 0xFFFFFF.
- parameter alpha: The opacity component of the color object, specified as a value from 0.0 to 1.0 (optional).
- returns: A UIColor initialized with the given color value.
*/
public convenience init(hex: UInt32, alpha: CGFloat = 1) {
assert((0...0xFFFFFF).contains(hex), "hex must be a value between 0x000000 and 0xFFFFFF")
assert((0...1).contains(alpha), "alpha must be a value between 0.0 and 1.0")
let (r, g, b) = Model.hex(hex).rgb
self.init(red: r/255, green: g/255, blue: b/255, alpha: alpha)
}
/**
Returns a UIColor from a hue-saturation-lightness (HSL) set.
- parameter hue: The hue component of the color object, specified as a value from 0.0 to 1.0.
- parameter saturation: The saturation component of the color object, specified as a value from 0.0 to 1.0.
- parameter lightness: The lightness component of the color object, specified as a value from 0.0 to 1.0.
- parameter alpha: The opacity component of the color object, specified as a value from 0.0 to 1.0 (optional).
- returns: A UIColor initialized with the given color value.
*/
public convenience init(hue: CGFloat, saturation: CGFloat, lightness: CGFloat, alpha: CGFloat = 1) {
assert((0...1).contains(hue), "hue value must be a value between 0.0 and 1.0")
assert((0...1).contains(saturation), "saturation must be a value between 0.0 and 1.0")
assert((0...1).contains(lightness), "lightness must be a value between 0.0 and 1.0")
assert((0...1).contains(alpha), "alpha must be a value between 0.0 and 1.0")
let (r, g, b) = Model.hsl(hue, saturation, lightness).rgb
self.init(red: r/255, green: g/255, blue: b/255, alpha: alpha)
}
/**
Returns a UIColor from a cyan-magenta-yellow-key (CMYK) set.
- parameter cyan: The cyan component of the color object, specified as a value from 0.0 to 1.0.
- parameter magenta: The magenta component of the color object, specified as a value from 0.0 to 1.0.
- parameter yellow: The yellow component of the color object, specified as a value from 0.0 to 1.0.
- parameter key: The key (black) component of the color object, specified as a value from 0.0 to 1.0.
- parameter alpha: The opacity component of the color object, specified as a value from 0.0 to 1.0 (optional).
- returns: A UIColor initialized with the given color value.
*/
public convenience init(cyan: CGFloat, magenta: CGFloat, yellow: CGFloat, key: CGFloat, alpha: CGFloat = 1) {
assert((0...1).contains(cyan), "cyan value must be a value between 0.0 and 1.0")
assert((0...1).contains(magenta), "magenta must be a value between 0.0 and 1.0")
assert((0...1).contains(yellow), "yellow must be a value between 0.0 and 1.0")
assert((0...1).contains(key), "key must be a value between 0.0 and 1.0")
assert((0...1).contains(alpha), "alpha must be a value between 0.0 and 1.0")
let (r, g, b) = Model.cmyk(cyan, magenta, yellow, key).rgb
self.init(red: r/255, green: g/255, blue: b/255, alpha: alpha)
}
/**
Returns a UIColor from a given hex color string.
- parameter hexString: The hex color string, e.g.: "#9443FB" or "9443FB".
- returns: A UIColor initialized with the color specified by the hexString.
*/
public convenience init(hexString: String, alpha: CGFloat = 1) {
var hexString = hexString
if hexString.hasPrefix("#") {
hexString = hexString[1...]
}
let scanner = Scanner(string: hexString)
var hexEquivalent: UInt32 = 0
if !scanner.scanHexInt32(&hexEquivalent) {
assertionFailure("hexString did not contain a valid hex value")
}
self.init(hex: hexEquivalent, alpha: alpha)
}
/**
Returns a UIColor initialized with color components divided by 255.0.
- parameter red: Integer representation of the red component in range of 0-255.
- parameter green: Integer representation of the green component in range of 0-255.
- parameter blue: Integer representation of the blue component in range of 0-255.
*/
public convenience init(red: UInt8, green: UInt8, blue: UInt8) {
self.init(red: CGFloat(red)/255.0, green: CGFloat(green)/255.0, blue: CGFloat(blue)/255.0, alpha: 1.0)
}
/// Returns a random UIColor with hue, saturation, and brightness ranging from 0.5 to 1.0.
public static var random: UIColor {
let component = { CGFloat(arc4random() % 128)/256.0 + 0.5 }
return UIColor(hue: component(), saturation: component(), brightness: component(), alpha: 1)
}
/**
Lightens the given color by the given percentage.
- parameter amount: The percentage by which to lighten the color. Valid values are from `0.0` to `1.0`, or for a more readable format `0%` to `100%`.
- returns: The lightened color.
*/
public final func lightened(by amount: CGFloat) -> UIColor {
assert((0...1).contains(amount), "amount must be in range 0-100%")
let (h, s, l) = hsl
return UIColor(hue: h, saturation: s, lightness: l * (1 + amount), alpha: rgba.a)
}
/**
Darkens the given color by the given percentage.
- parameter amount: The percentage by which to darken the color. Valid values are from `0.0` to `1.0`, or for a more readable format `0%` to `100%`.
- returns: The darkened color.
*/
public final func darkened(by amount: CGFloat) -> UIColor {
assert((0...1).contains(amount), "amount must be in range 0-100%")
let (h, s, l) = hsl
return UIColor(hue: h, saturation: s, lightness: l * (1 - amount), alpha: rgba.a)
}
/**
Returns the color represenation as a hexadecimal string, prefixed with '#'.
- returns: The hexadecimal string representation of the color.
*/
public final var hexString: String {
return String(format:"#%06x", hex)
}
/**
Returns the color representation as a 32-bit integer.
- returns: A UInt32 that represents the hexadecimal color.
*/
public final var hex: UInt32 {
let (r, g, b, _) = self.rgba
return Model.rgb(r * 255, g * 255, b * 255).hex
}
/**
Returns the RGBA (red, green, blue, alpha) components, specified as values from 0.0 to 1.0.
- returns: The RGBA components as a tuple (r, g, b, a).
*/
public final var rgba: (r: CGFloat, g: CGFloat, b: CGFloat, a: CGFloat) {
var (r, g, b, a): (CGFloat, CGFloat, CGFloat, CGFloat) = (0, 0, 0, 0)
getRed(&r, green: &g, blue: &b, alpha: &a)
return (r, g, b, a)
}
/**
Returns the HSL (hue, saturation, lightness) components, specified as values from 0.0 to 1.0.
- returns: The HSL components as a tuple (h, s, l).
*/
public final var hsl: (h: CGFloat, s: CGFloat, l: CGFloat) {
let (r, g, b, _) = rgba
return Model.rgb(r * 255, g * 255, b * 255).hsl
}
/**
Returns the HSB (hue, saturation, brightness) components, specified as values from 0.0 to 1.0.
- returns: The HSB components as a tuple (h, s, b).
*/
public final var hsb: (h: CGFloat, s: CGFloat, b: CGFloat) {
let (r, g, b, _) = rgba
return Model.rgb(r * 255, g * 255, b * 255).hsb
}
/**
Returns the CMYK (cyan, magenta, yellow, key) components, specified as values from 0.0 to 1.0.
- returns: The CMYK components as a tuple (c, m, y, k).
*/
public final var cmyk: (c: CGFloat, m: CGFloat, y: CGFloat, k: CGFloat) {
let (r, g, b, _) = rgba
return Model.rgb(r * 255, g * 255, b * 255).cmyk
}
/**
Returns an alpha-adjusted UIColor.
- returns: A UIColor with an adjust alpha component (shorthand for `colorWithAlphaComponent`).
*/
public final func alpha(_ alpha: CGFloat) -> UIColor {
return withAlphaComponent(alpha)
}
/**
Returns a UIColor from the given hexidecimal integer.
- parameter hex: The color value.
- parameter alpha: The alpha component.
- returns: A UIColor initialized with the given hex value.
*/
public static func hex(_ hex: UInt32, alpha: CGFloat = 1) -> UIColor {
return UIColor(hex: hex, alpha: alpha)
}
/**
Returns a UIColor from the given RGB components.
- parameter red: The red component in range of 0-255.
- parameter green: The green component in range of 0-255.
- parameter blue: The blue component in range of 0-255.
- parameter alpha: The alpha component.
- returns: A UIColor initialized with the given RGB components.
*/
public static func rgb(_ red: UInt8, _ green: UInt8, _ blue: UInt8, alpha: CGFloat = 1) -> UIColor {
return UIColor(red: red, green: green, blue: blue).alpha(alpha)
}
}
// MARK: - Model
extension UIColor {
/**
Model is an enum for describing and converting color models.
- `rgb`: Red, Green, Blue color representation
- `hsl`: Hue, Saturation, Lightness color representation
- `hsb`: Hue, Saturation, Brightness color representation
- `cmyk`: Cyan, Magenta, Yellow, Key (Black) color representation
- `hex`: UInt32 (hex) color representation
*/
public enum Model {
/// Red, Green, Blue
case rgb(CGFloat, CGFloat, CGFloat)
/// Hue, Saturation, Lightness
case hsl(CGFloat, CGFloat, CGFloat)
/// Hue, Saturation, Brightness
case hsb(CGFloat, CGFloat, CGFloat)
/// Cyan, Magenta, Yellow, Key (Black)
case cmyk(CGFloat, CGFloat, CGFloat, CGFloat)
/// UInt32 (hex)
case hex(UInt32)
/// Returns the model as an RGB tuple
public var rgb: (r: CGFloat, g: CGFloat, b: CGFloat) {
switch self {
case .rgb(let rgb):
return rgb
case .hsl(let h, let s, let l):
return convert(hsl: h, s, l)
case .hsb(let h, let s, let b):
return convert(hsb: h, s, b)
case .cmyk(let c, let m, let y, let k):
return convert(cmyk: c, m, y, k)
case .hex(let hex):
return convert(hex: hex)
}
}
/// Returns the model as an HSL tuple
public var hsl: (h: CGFloat, s: CGFloat, l: CGFloat) {
switch self {
case .rgb(let r, let g, let b):
return convert(rgb: r, g, b)
case .hsl(let hsl):
return hsl
case .hsb, .cmyk, .hex:
let (r, g, b) = self.rgb
return convert(rgb: r, g, b)
}
}
/// Returns the model as an HSB tuple
public var hsb: (h: CGFloat, s: CGFloat, b: CGFloat) {
switch self {
case .rgb(let r, let g, let b):
return convert(rgb: r, g, b)
case .hsl, .cmyk, .hex:
let (r, g, b) = self.rgb
return convert(rgb: r, g, b)
case .hsb(let hsb):
return hsb
}
}
/// Returns the model as a CMYK tuple
public var cmyk: (c: CGFloat, m: CGFloat, y: CGFloat, k: CGFloat) {
switch self {
case .rgb(let r, let g, let b):
return convert(rgb: r, g, b)
case .hsl, .hsb, .hex:
let (r, g, b) = self.rgb
return convert(rgb: r, g, b)
case .cmyk(let cmyk):
return cmyk
}
}
/// Returns the model as a UInt32 (hex) value
public var hex: UInt32 {
switch self {
case .rgb(let r, let g, let b):
return convert(rgb: r, g, b)
case .hsl, .hsb, .cmyk:
let (r, g, b) = self.rgb
return convert(rgb: r, g, b)
case .hex(let hex):
return hex
}
}
}
}
// MARK: - Private color model conversions
/// Converts RGB to HSL (https://en.wikipedia.org/wiki/HSL_and_HSV)
private func convert(rgb r: CGFloat, _ g: CGFloat, _ b: CGFloat) -> (h: CGFloat, s: CGFloat, l: CGFloat) {
let r = r / 255
let g = g / 255
let b = b / 255
let max = Swift.max(r, g, b)
let min = Swift.min(r, g, b)
var h, s: CGFloat
let l = (max + min) / 2
if max == min {
h = 0
s = 0
}
else {
let d = max - min
s = (l > 0.5) ? d / (2 - max - min) : d / (max + min)
switch max {
case r: h = (g - b) / d + (g < b ? 6 : 0)
case g: h = (b - r) / d + 2
case b: h = (r - g) / d + 4
default: h = 0
}
h /= 6;
}
return (h, s, l)
}
/// Converts HSL to RGB (https://en.wikipedia.org/wiki/HSL_and_HSV)
private func convert(hsl h: CGFloat, _ s: CGFloat, _ l: CGFloat) -> (r: CGFloat, g: CGFloat, b: CGFloat) {
let r, g, b: CGFloat
if s == 0 {
r = l
g = l
b = l
}
else {
let c = (1 - abs(2 * l - 1)) * s
let x = c * (1 - abs((h * 6).truncatingRemainder(dividingBy: 2) - 1))
let m = l - c/2
switch h * 6 {
case 0..<1: (r, g, b) = (c, x, 0) + m
case 1..<2: (r, g, b) = (x, c, 0) + m
case 2..<3: (r, g, b) = (0, c, x) + m
case 3..<4: (r, g, b) = (0, x, c) + m
case 4..<5: (r, g, b) = (x, 0, c) + m
case 5..<6: (r, g, b) = (c, 0, x) + m
default: (r, g, b) = (0, 0, 0) + m
}
}
return (round(r * 255), round(g * 255), round(b * 255))
}
/// Converts RGB to HSB (https://en.wikipedia.org/wiki/HSL_and_HSV)
private func convert(rgb r: CGFloat, _ g: CGFloat, _ b: CGFloat) -> (h: CGFloat, s: CGFloat, b: CGFloat) {
var h, s, v: CGFloat
let r = r / 255
let g = g / 255
let b = b / 255
let max = Swift.max(r, g, b)
let min = Swift.min(r, g, b)
let d = max - min
if d == 0 {
h = 0
s = 0
}
else {
s = (max == 0) ? 0 : d / max
switch max {
case r: h = ((g - b) / d) + (g < b ? 6 : 0)
case g: h = ((b - r) / d) + 2
case b: h = ((r - g) / d) + 4
default: h = 0
}
h /= 6;
}
v = max
return (h, s, v)
}
/// Converts HSB to RGB (https://en.wikipedia.org/wiki/HSL_and_HSV)
private func convert(hsb h: CGFloat, _ s: CGFloat, _ b: CGFloat) -> (r: CGFloat, g: CGFloat, b: CGFloat) {
let c = b * s
let x = c * (1 - abs((h * 6).truncatingRemainder(dividingBy: 2) - 1))
let m = b - c
var r, g, b: CGFloat
switch h * 6 {
case 0..<1: (r, g, b) = (c, x, 0) + m
case 1..<2: (r, g, b) = (x, c, 0) + m
case 2..<3: (r, g, b) = (0, c, x) + m
case 3..<4: (r, g, b) = (0, x, c) + m
case 4..<5: (r, g, b) = (x, 0, c) + m
case 5..<6: (r, g, b) = (c, 0, x) + m
default: (r, g, b) = (0, 0, 0) + m
}
return (round(r * 255), round(g * 255), round(b * 255))
}
/// Converts UInt32 to RGB
private func convert(hex: UInt32) -> (r: CGFloat, g: CGFloat, b: CGFloat) {
let r = CGFloat((hex >> 16) & 0xFF)
let g = CGFloat((hex >> 8) & 0xFF)
let b = CGFloat(hex & 0xFF)
return (r, g, b)
}
/// Converts RGB to UInt32
private func convert(rgb r: CGFloat, _ g: CGFloat, _ b: CGFloat) -> UInt32 {
return (UInt32(r) << 16) | (UInt32(g) << 8) | UInt32(b)
}
/// Converts RGB to CMYK (http://www.rapidtables.com/convert/color/rgb-to-cmyk.htm)
private func convert(rgb r: CGFloat, _ g: CGFloat, _ b: CGFloat) -> (c: CGFloat, m: CGFloat, y: CGFloat, k: CGFloat) {
let r = r / 255
let g = g / 255
let b = b / 255
let k = 1 - max(r, g, b)
let c = (k == 1) ? 0 : (1 - r - k) / (1 - k)
let m = (k == 1) ? 0 : (1 - g - k) / (1 - k)
let y = (k == 1) ? 0 : (1 - b - k) / (1 - k)
return (c, m, y, k)
}
/// Converts CMYK to RGB (http://www.rapidtables.com/convert/color/cmyk-to-rgb.htm)
private func convert(cmyk c: CGFloat, _ m: CGFloat, _ y: CGFloat, _ k: CGFloat) -> (r: CGFloat, g: CGFloat, b: CGFloat) {
let r = 255 * (1 - c) * (1 - k)
let g = 255 * (1 - m) * (1 - k)
let b = 255 * (1 - y) * (1 - k)
return (round(r), round(g), round(b))
}
/// Private operator for HSL and HSB conversion
private func +(lhs: (CGFloat, CGFloat, CGFloat), rhs: CGFloat) -> (CGFloat, CGFloat, CGFloat) {
return (lhs.0 + rhs, lhs.1 + rhs, lhs.2 + rhs)
}
-14
View File
@@ -1,14 +0,0 @@
import Foundation
import UIKit
extension UIColor {
/// Returns a random UIColor
public static var random: UIColor {
let hue : CGFloat = CGFloat(arc4random() % 256) / 256 // use 256 to get full range from 0.0 to 1.0
let saturation : CGFloat = CGFloat(arc4random() % 128) / 256 + 0.5 // from 0.5 to 1.0 to stay away from white
let brightness : CGFloat = CGFloat(arc4random() % 128) / 256 + 0.5 // from 0.5 to 1.0 to stay away from black
return UIColor(hue: hue, saturation: saturation, brightness: brightness, alpha: 1)
}
}