Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 54dbcc8c2e | |||
| 6c92596228 | |||
| 9b222b328e | |||
| 64d0f96f83 | |||
| 7556ac505e | |||
| 96abfee256 | |||
| 86a06411d7 | |||
| 8031f5a28f | |||
| fdd2ed5b7c | |||
| 5cabfab574 | |||
| 535b76fea0 | |||
| 5b0672ee60 |
@@ -1,5 +1,5 @@
|
||||
PODS:
|
||||
- utopia (0.1.0)
|
||||
- utopia (0.2.1)
|
||||
|
||||
DEPENDENCIES:
|
||||
- utopia (from `../`)
|
||||
@@ -9,8 +9,8 @@ EXTERNAL SOURCES:
|
||||
:path: "../"
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
utopia: 62f8a2a9add7c9b605c03e8c20972831a569a084
|
||||
utopia: 5f8b06c09df6ecc26f8f64a01bce85b887cfb4ab
|
||||
|
||||
PODFILE CHECKSUM: 632947ed90777377758aa9384f691613d327ceb4
|
||||
|
||||
COCOAPODS: 1.5.3
|
||||
COCOAPODS: 1.8.4
|
||||
|
||||
+4
-4
@@ -1,8 +1,7 @@
|
||||
{
|
||||
"name": "utopia",
|
||||
"version": "0.1.0",
|
||||
"summary": "A short description of utopia.",
|
||||
"description": "TODO: Add long description of the pod here.",
|
||||
"version": "0.2.1",
|
||||
"summary": "Common utilities for Swift projects",
|
||||
"homepage": "https://github.com/Ramotion/utopia",
|
||||
"license": {
|
||||
"type": "MIT",
|
||||
@@ -10,10 +9,11 @@
|
||||
},
|
||||
"authors": {
|
||||
"Dmitriy Kalachev": "dima.k@ramotion.com"
|
||||
"Ramotion": "igor.k@ramotion.com"
|
||||
},
|
||||
"source": {
|
||||
"git": "https://github.com/Ramotion/utopia.git",
|
||||
"tag": "0.1.0"
|
||||
"tag": "0.2.1"
|
||||
},
|
||||
"platforms": {
|
||||
"ios": "10.0"
|
||||
|
||||
Generated
+3
-3
@@ -1,5 +1,5 @@
|
||||
PODS:
|
||||
- utopia (0.1.0)
|
||||
- utopia (0.2.1)
|
||||
|
||||
DEPENDENCIES:
|
||||
- utopia (from `../`)
|
||||
@@ -9,8 +9,8 @@ EXTERNAL SOURCES:
|
||||
:path: "../"
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
utopia: 62f8a2a9add7c9b605c03e8c20972831a569a084
|
||||
utopia: 5f8b06c09df6ecc26f8f64a01bce85b887cfb4ab
|
||||
|
||||
PODFILE CHECKSUM: 632947ed90777377758aa9384f691613d327ceb4
|
||||
|
||||
COCOAPODS: 1.5.3
|
||||
COCOAPODS: 1.8.4
|
||||
|
||||
+737
-713
File diff suppressed because it is too large
Load Diff
+26
@@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>${EXECUTABLE_NAME}</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>${PRODUCT_BUNDLE_IDENTIFIER}</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>${PRODUCT_NAME}</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0.0</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>${CURRENT_PROJECT_VERSION}</string>
|
||||
<key>NSPrincipalClass</key>
|
||||
<string></string>
|
||||
</dict>
|
||||
</plist>
|
||||
+28
-10
@@ -3,10 +3,15 @@ set -e
|
||||
set -u
|
||||
set -o pipefail
|
||||
|
||||
function on_error {
|
||||
echo "$(realpath -mq "${0}"):$1: error: Unexpected failure"
|
||||
}
|
||||
trap 'on_error $LINENO' ERR
|
||||
|
||||
if [ -z ${FRAMEWORKS_FOLDER_PATH+x} ]; then
|
||||
# If FRAMEWORKS_FOLDER_PATH is not set, then there's nowhere for us to copy
|
||||
# frameworks to, so exit 0 (signalling the script phase was successful).
|
||||
exit 0
|
||||
# If FRAMEWORKS_FOLDER_PATH is not set, then there's nowhere for us to copy
|
||||
# frameworks to, so exit 0 (signalling the script phase was successful).
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "mkdir -p ${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
|
||||
@@ -36,8 +41,8 @@ install_framework()
|
||||
local destination="${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
|
||||
|
||||
if [ -L "${source}" ]; then
|
||||
echo "Symlinked..."
|
||||
source="$(readlink "${source}")"
|
||||
echo "Symlinked..."
|
||||
source="$(readlink "${source}")"
|
||||
fi
|
||||
|
||||
# Use filter instead of exclude so missing patterns don't throw errors.
|
||||
@@ -47,8 +52,13 @@ install_framework()
|
||||
local basename
|
||||
basename="$(basename -s .framework "$1")"
|
||||
binary="${destination}/${basename}.framework/${basename}"
|
||||
|
||||
if ! [ -r "$binary" ]; then
|
||||
binary="${destination}/${basename}"
|
||||
elif [ -L "${binary}" ]; then
|
||||
echo "Destination binary is symlinked..."
|
||||
dirname="$(dirname "${binary}")"
|
||||
binary="${dirname}/$(readlink "${binary}")"
|
||||
fi
|
||||
|
||||
# Strip invalid architectures so "fat" simulator / device frameworks work on device
|
||||
@@ -62,7 +72,7 @@ install_framework()
|
||||
# Embed linked Swift runtime libraries. No longer necessary as of Xcode 7.
|
||||
if [ "${XCODE_VERSION_MAJOR}" -lt 7 ]; then
|
||||
local swift_runtime_libs
|
||||
swift_runtime_libs=$(xcrun otool -LX "$binary" | grep --color=never @rpath/libswift | sed -E s/@rpath\\/\(.+dylib\).*/\\1/g | uniq -u && exit ${PIPESTATUS[0]})
|
||||
swift_runtime_libs=$(xcrun otool -LX "$binary" | grep --color=never @rpath/libswift | sed -E s/@rpath\\/\(.+dylib\).*/\\1/g | uniq -u)
|
||||
for lib in $swift_runtime_libs; do
|
||||
echo "rsync -auv \"${SWIFT_STDLIB_PATH}/${lib}\" \"${destination}\""
|
||||
rsync -auv "${SWIFT_STDLIB_PATH}/${lib}" "${destination}"
|
||||
@@ -84,7 +94,7 @@ install_dsym() {
|
||||
binary="${DERIVED_FILES_DIR}/${basename}.framework.dSYM/Contents/Resources/DWARF/${basename}"
|
||||
|
||||
# Strip invalid architectures so "fat" simulator / device frameworks work on device
|
||||
if [[ "$(file "$binary")" == *"Mach-O dSYM companion"* ]]; then
|
||||
if [[ "$(file "$binary")" == *"Mach-O "*"dSYM companion"* ]]; then
|
||||
strip_invalid_archs "$binary"
|
||||
fi
|
||||
|
||||
@@ -99,10 +109,18 @@ install_dsym() {
|
||||
fi
|
||||
}
|
||||
|
||||
# Copies the bcsymbolmap files of a vendored framework
|
||||
install_bcsymbolmap() {
|
||||
local bcsymbolmap_path="$1"
|
||||
local destination="${BUILT_PRODUCTS_DIR}"
|
||||
echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${bcsymbolmap_path}" "${destination}""
|
||||
rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${bcsymbolmap_path}" "${destination}"
|
||||
}
|
||||
|
||||
# Signs a framework with the provided identity
|
||||
code_sign_if_enabled() {
|
||||
if [ -n "${EXPANDED_CODE_SIGN_IDENTITY}" -a "${CODE_SIGNING_REQUIRED:-}" != "NO" -a "${CODE_SIGNING_ALLOWED}" != "NO" ]; then
|
||||
# Use the current code_sign_identitiy
|
||||
if [ -n "${EXPANDED_CODE_SIGN_IDENTITY:-}" -a "${CODE_SIGNING_REQUIRED:-}" != "NO" -a "${CODE_SIGNING_ALLOWED}" != "NO" ]; then
|
||||
# Use the current code_sign_identity
|
||||
echo "Code Signing $1 with Identity ${EXPANDED_CODE_SIGN_IDENTITY_NAME}"
|
||||
local code_sign_cmd="/usr/bin/codesign --force --sign ${EXPANDED_CODE_SIGN_IDENTITY} ${OTHER_CODE_SIGN_FLAGS:-} --preserve-metadata=identifier,entitlements '$1'"
|
||||
|
||||
@@ -131,7 +149,7 @@ strip_invalid_archs() {
|
||||
for arch in $binary_archs; do
|
||||
if ! [[ "${ARCHS}" == *"$arch"* ]]; then
|
||||
# Strip non-valid architectures in-place
|
||||
lipo -remove "$arch" -output "$binary" "$binary" || exit 1
|
||||
lipo -remove "$arch" -output "$binary" "$binary"
|
||||
stripped="$stripped $arch"
|
||||
fi
|
||||
done
|
||||
|
||||
+3
-2
@@ -1,11 +1,12 @@
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES
|
||||
FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/utopia"
|
||||
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
|
||||
HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/utopia/utopia.framework/Headers"
|
||||
LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks'
|
||||
OTHER_CFLAGS = $(inherited) -iquote "${PODS_CONFIGURATION_BUILD_DIR}/utopia/utopia.framework/Headers"
|
||||
OTHER_LDFLAGS = $(inherited) -framework "utopia"
|
||||
OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS"
|
||||
OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS
|
||||
PODS_BUILD_DIR = ${BUILD_DIR}
|
||||
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
|
||||
PODS_PODFILE_DIR_PATH = ${SRCROOT}/.
|
||||
PODS_ROOT = ${SRCROOT}/Pods
|
||||
USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES
|
||||
|
||||
Generated
+3
-2
@@ -1,11 +1,12 @@
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES
|
||||
FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/utopia"
|
||||
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
|
||||
HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/utopia/utopia.framework/Headers"
|
||||
LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks'
|
||||
OTHER_CFLAGS = $(inherited) -iquote "${PODS_CONFIGURATION_BUILD_DIR}/utopia/utopia.framework/Headers"
|
||||
OTHER_LDFLAGS = $(inherited) -framework "utopia"
|
||||
OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS"
|
||||
OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS
|
||||
PODS_BUILD_DIR = ${BUILD_DIR}
|
||||
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
|
||||
PODS_PODFILE_DIR_PATH = ${SRCROOT}/.
|
||||
PODS_ROOT = ${SRCROOT}/Pods
|
||||
USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>${EXECUTABLE_NAME}</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>${PRODUCT_BUNDLE_IDENTIFIER}</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>${PRODUCT_NAME}</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>0.2.1</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>${CURRENT_PROJECT_VERSION}</string>
|
||||
<key>NSPrincipalClass</key>
|
||||
<string></string>
|
||||
</dict>
|
||||
</plist>
|
||||
+2
-1
@@ -1,9 +1,10 @@
|
||||
CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/utopia
|
||||
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
|
||||
OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS"
|
||||
OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS
|
||||
PODS_BUILD_DIR = ${BUILD_DIR}
|
||||
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
|
||||
PODS_ROOT = ${SRCROOT}
|
||||
PODS_TARGET_SRCROOT = ${PODS_ROOT}/../..
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier}
|
||||
SKIP_INSTALL = YES
|
||||
USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES
|
||||
|
||||
@@ -136,18 +136,18 @@
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastSwiftUpdateCheck = 0830;
|
||||
LastUpgradeCheck = 1000;
|
||||
LastUpgradeCheck = 1110;
|
||||
ORGANIZATIONNAME = CocoaPods;
|
||||
TargetAttributes = {
|
||||
607FACCF1AFB9204008FA782 = {
|
||||
CreatedOnToolsVersion = 6.3.1;
|
||||
LastSwiftMigration = 1000;
|
||||
LastSwiftMigration = 1110;
|
||||
};
|
||||
};
|
||||
};
|
||||
buildConfigurationList = 607FACCB1AFB9204008FA782 /* Build configuration list for PBXProject "utopia" */;
|
||||
compatibilityVersion = "Xcode 3.2";
|
||||
developmentRegion = English;
|
||||
developmentRegion = en;
|
||||
hasScannedForEncodings = 0;
|
||||
knownRegions = (
|
||||
en,
|
||||
@@ -182,7 +182,7 @@
|
||||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
"${SRCROOT}/Pods/Target Support Files/Pods-utopia_Example/Pods-utopia_Example-frameworks.sh",
|
||||
"${PODS_ROOT}/Target Support Files/Pods-utopia_Example/Pods-utopia_Example-frameworks.sh",
|
||||
"${BUILT_PRODUCTS_DIR}/utopia/utopia.framework",
|
||||
);
|
||||
name = "[CP] Embed Pods Frameworks";
|
||||
@@ -191,7 +191,7 @@
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-utopia_Example/Pods-utopia_Example-frameworks.sh\"\n";
|
||||
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-utopia_Example/Pods-utopia_Example-frameworks.sh\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
466881BFDDF35CB453DC3935 /* [CP] Check Pods Manifest.lock */ = {
|
||||
@@ -241,6 +241,7 @@
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
@@ -296,6 +297,7 @@
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
@@ -351,7 +353,7 @@
|
||||
MODULE_NAME = ExampleApp;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.$(PRODUCT_NAME:rfc1034identifier)";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 4.2;
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
@@ -366,7 +368,7 @@
|
||||
MODULE_NAME = ExampleApp;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.$(PRODUCT_NAME:rfc1034identifier)";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 4.2;
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1000"
|
||||
LastUpgradeVersion = "1110"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
@@ -41,6 +41,15 @@
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "607FACCF1AFB9204008FA782"
|
||||
BuildableName = "utopia_Example.app"
|
||||
BlueprintName = "utopia_Example"
|
||||
ReferencedContainer = "container:utopia.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<Testables>
|
||||
<TestableReference
|
||||
skipped = "NO">
|
||||
@@ -53,17 +62,6 @@
|
||||
</BuildableReference>
|
||||
</TestableReference>
|
||||
</Testables>
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "607FACCF1AFB9204008FA782"
|
||||
BuildableName = "utopia_Example.app"
|
||||
BlueprintName = "utopia_Example"
|
||||
ReferencedContainer = "container:utopia.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
@@ -85,8 +83,6 @@
|
||||
ReferencedContainer = "container:utopia.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
Pod::Spec.new do |s|
|
||||
s.name = 'utopia'
|
||||
s.version = '0.1.0'
|
||||
s.version = '0.2.1'
|
||||
s.summary = 'Common utilities for Swift projects'
|
||||
s.homepage = 'https://github.com/Ramotion/utopia'
|
||||
s.license = { :type => 'MIT', :file => 'LICENSE' }
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ public extension Dictionary {
|
||||
}
|
||||
}
|
||||
|
||||
extension Optional where Wrapped: Collection {
|
||||
public extension Optional where Wrapped: Collection {
|
||||
var isNilOrEmpty: Bool {
|
||||
switch self {
|
||||
case let collection?:
|
||||
@@ -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]
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
|
||||
extension Optional where Wrapped == String {
|
||||
public extension Optional where Wrapped == String {
|
||||
var isNilOrEmpty: Bool {
|
||||
switch self {
|
||||
case let string?:
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
+4
-4
@@ -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
|
||||
}
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -7,9 +7,9 @@ private class Associated<T>: NSObject {
|
||||
}
|
||||
}
|
||||
|
||||
protocol Associable {}
|
||||
public protocol Associable {}
|
||||
|
||||
extension Associable where Self: AnyObject {
|
||||
public extension Associable where Self: AnyObject {
|
||||
|
||||
func getAssociatedObject<T>(_ key: UnsafeRawPointer) -> T? {
|
||||
return (objc_getAssociatedObject(self, key) as? Associated<T>).map { $0.value }
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,24 @@ public enum Math {
|
||||
public static func lerp<T: FloatingPoint>(from: T, to: T, progress: T) -> T {
|
||||
return from + progress * (to - from);
|
||||
}
|
||||
|
||||
public static func lerp(from: UIColor, to: UIColor, progress: CGFloat) -> UIColor {
|
||||
let rgba1 = from.rgba
|
||||
let rgba2 = to.rgba
|
||||
let r = lerp(from: rgba1.r, to: rgba2.r, progress: progress)
|
||||
let g = lerp(from: rgba1.g, to: rgba2.g, progress: progress)
|
||||
let b = lerp(from: rgba1.b, to: rgba2.b, progress: progress)
|
||||
let a = lerp(from: rgba1.a, to: rgba2.a, progress: progress)
|
||||
return UIColor(red: r, green: g, blue: b, alpha: a)
|
||||
}
|
||||
|
||||
public static func lerp(from: CGRect, to: CGRect, progress: CGFloat) -> CGRect {
|
||||
let x = lerp(from: from.origin.x, to: to.origin.x, progress: progress)
|
||||
let y = lerp(from: from.origin.y, to: to.origin.y, progress: progress)
|
||||
let w = lerp(from: from.size.width, to: to.size.width, progress: progress)
|
||||
let h = lerp(from: from.size.height, to: to.size.height, progress: progress)
|
||||
return CGRect(x: x, y: y, width: w, height: h)
|
||||
}
|
||||
}
|
||||
|
||||
public enum Progress {
|
||||
|
||||
@@ -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 paragraph’s 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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user