mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2026-02-05 18:58:51 +00:00
Refactored PasscodeInputFieldNode
This commit is contained in:
17
submodules/PasscodeInputFieldNode/BUCK
Normal file
17
submodules/PasscodeInputFieldNode/BUCK
Normal file
@@ -0,0 +1,17 @@
|
||||
load("//Config:buck_rule_macros.bzl", "static_library")
|
||||
|
||||
static_library(
|
||||
name = "PasscodeInputFieldNode",
|
||||
srcs = glob([
|
||||
"Sources/**/*.swift",
|
||||
]),
|
||||
deps = [
|
||||
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit#shared",
|
||||
"//submodules/AsyncDisplayKit:AsyncDisplayKit#shared",
|
||||
"//submodules/Display:Display#shared",
|
||||
],
|
||||
frameworks = [
|
||||
"$SDKROOT/System/Library/Frameworks/Foundation.framework",
|
||||
"$SDKROOT/System/Library/Frameworks/UIKit.framework",
|
||||
],
|
||||
)
|
||||
22
submodules/PasscodeInputFieldNode/Info.plist
Normal file
22
submodules/PasscodeInputFieldNode/Info.plist
Normal file
@@ -0,0 +1,22 @@
|
||||
<?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>$(DEVELOPMENT_LANGUAGE)</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</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,401 @@
|
||||
<?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>archiveVersion</key>
|
||||
<string>1</string>
|
||||
<key>classes</key>
|
||||
<dict>
|
||||
</dict>
|
||||
<key>objectVersion</key>
|
||||
<string>46</string>
|
||||
<key>objects</key>
|
||||
<dict>
|
||||
<key>1DD70E2954723C0500000000</key>
|
||||
<dict>
|
||||
<key>isa</key>
|
||||
<string>PBXFileReference</string>
|
||||
<key>name</key>
|
||||
<string>PasscodeInputFieldNode-Debug.xcconfig</string>
|
||||
<key>path</key>
|
||||
<string>../../buck-out/gen/submodules/PasscodeInputFieldNode/PasscodeInputFieldNode-Debug.xcconfig</string>
|
||||
<key>sourceTree</key>
|
||||
<string>SOURCE_ROOT</string>
|
||||
<key>explicitFileType</key>
|
||||
<string>text.xcconfig</string>
|
||||
</dict>
|
||||
<key>1DD70E29E5F5E62F00000000</key>
|
||||
<dict>
|
||||
<key>isa</key>
|
||||
<string>PBXFileReference</string>
|
||||
<key>name</key>
|
||||
<string>PasscodeInputFieldNode-Profile.xcconfig</string>
|
||||
<key>path</key>
|
||||
<string>../../buck-out/gen/submodules/PasscodeInputFieldNode/PasscodeInputFieldNode-Profile.xcconfig</string>
|
||||
<key>sourceTree</key>
|
||||
<string>SOURCE_ROOT</string>
|
||||
<key>explicitFileType</key>
|
||||
<string>text.xcconfig</string>
|
||||
</dict>
|
||||
<key>1DD70E29598C919100000000</key>
|
||||
<dict>
|
||||
<key>isa</key>
|
||||
<string>PBXFileReference</string>
|
||||
<key>name</key>
|
||||
<string>PasscodeInputFieldNode-Release.xcconfig</string>
|
||||
<key>path</key>
|
||||
<string>../../buck-out/gen/submodules/PasscodeInputFieldNode/PasscodeInputFieldNode-Release.xcconfig</string>
|
||||
<key>sourceTree</key>
|
||||
<string>SOURCE_ROOT</string>
|
||||
<key>explicitFileType</key>
|
||||
<string>text.xcconfig</string>
|
||||
</dict>
|
||||
<key>B401C9792F7F325000000000</key>
|
||||
<dict>
|
||||
<key>isa</key>
|
||||
<string>PBXGroup</string>
|
||||
<key>name</key>
|
||||
<string>Buck (Do Not Modify)</string>
|
||||
<key>sourceTree</key>
|
||||
<string><![CDATA[<group>]]></string>
|
||||
<key>children</key>
|
||||
<array>
|
||||
<string>1DD70E2954723C0500000000</string>
|
||||
<string>1DD70E29E5F5E62F00000000</string>
|
||||
<string>1DD70E29598C919100000000</string>
|
||||
</array>
|
||||
</dict>
|
||||
<key>B401C979B781F65D00000000</key>
|
||||
<dict>
|
||||
<key>isa</key>
|
||||
<string>PBXGroup</string>
|
||||
<key>name</key>
|
||||
<string>Configurations</string>
|
||||
<key>sourceTree</key>
|
||||
<string><![CDATA[<group>]]></string>
|
||||
<key>children</key>
|
||||
<array>
|
||||
<string>B401C9792F7F325000000000</string>
|
||||
</array>
|
||||
</dict>
|
||||
<key>1DD70E29FF334B1F00000000</key>
|
||||
<dict>
|
||||
<key>isa</key>
|
||||
<string>PBXFileReference</string>
|
||||
<key>name</key>
|
||||
<string>libDisplay.dylib</string>
|
||||
<key>path</key>
|
||||
<string>libDisplay.dylib</string>
|
||||
<key>sourceTree</key>
|
||||
<string>BUILT_PRODUCTS_DIR</string>
|
||||
<key>explicitFileType</key>
|
||||
<string>compiled.mach-o.dylib</string>
|
||||
</dict>
|
||||
<key>1DD70E29D65BA68200000000</key>
|
||||
<dict>
|
||||
<key>isa</key>
|
||||
<string>PBXFileReference</string>
|
||||
<key>name</key>
|
||||
<string>libSwiftSignalKit.dylib</string>
|
||||
<key>path</key>
|
||||
<string>libSwiftSignalKit.dylib</string>
|
||||
<key>sourceTree</key>
|
||||
<string>BUILT_PRODUCTS_DIR</string>
|
||||
<key>explicitFileType</key>
|
||||
<string>compiled.mach-o.dylib</string>
|
||||
</dict>
|
||||
<key>B401C97968022A5500000000</key>
|
||||
<dict>
|
||||
<key>isa</key>
|
||||
<string>PBXGroup</string>
|
||||
<key>name</key>
|
||||
<string>Frameworks</string>
|
||||
<key>sourceTree</key>
|
||||
<string><![CDATA[<group>]]></string>
|
||||
<key>children</key>
|
||||
<array>
|
||||
<string>1DD70E29FF334B1F00000000</string>
|
||||
<string>1DD70E29D65BA68200000000</string>
|
||||
</array>
|
||||
</dict>
|
||||
<key>1DD70E29001F47FB00000000</key>
|
||||
<dict>
|
||||
<key>isa</key>
|
||||
<string>PBXFileReference</string>
|
||||
<key>name</key>
|
||||
<string>BUCK</string>
|
||||
<key>path</key>
|
||||
<string>BUCK</string>
|
||||
<key>sourceTree</key>
|
||||
<string>SOURCE_ROOT</string>
|
||||
<key>explicitFileType</key>
|
||||
<string>text.script.python</string>
|
||||
</dict>
|
||||
<key>1DD70E290BCAAD5500000000</key>
|
||||
<dict>
|
||||
<key>isa</key>
|
||||
<string>PBXFileReference</string>
|
||||
<key>name</key>
|
||||
<string>PasscodeInputFieldNode.swift</string>
|
||||
<key>path</key>
|
||||
<string>Sources/PasscodeInputFieldNode.swift</string>
|
||||
<key>sourceTree</key>
|
||||
<string>SOURCE_ROOT</string>
|
||||
</dict>
|
||||
<key>B401C979EAB5339800000000</key>
|
||||
<dict>
|
||||
<key>isa</key>
|
||||
<string>PBXGroup</string>
|
||||
<key>name</key>
|
||||
<string>Sources</string>
|
||||
<key>sourceTree</key>
|
||||
<string><![CDATA[<group>]]></string>
|
||||
<key>children</key>
|
||||
<array>
|
||||
<string>1DD70E290BCAAD5500000000</string>
|
||||
</array>
|
||||
</dict>
|
||||
<key>B401C979928346B000000000</key>
|
||||
<dict>
|
||||
<key>isa</key>
|
||||
<string>PBXGroup</string>
|
||||
<key>name</key>
|
||||
<string>PasscodeInputFieldNode</string>
|
||||
<key>sourceTree</key>
|
||||
<string><![CDATA[<group>]]></string>
|
||||
<key>children</key>
|
||||
<array>
|
||||
<string>1DD70E29001F47FB00000000</string>
|
||||
<string>B401C979EAB5339800000000</string>
|
||||
</array>
|
||||
</dict>
|
||||
<key>1DD70E2989A0042800000000</key>
|
||||
<dict>
|
||||
<key>isa</key>
|
||||
<string>PBXFileReference</string>
|
||||
<key>name</key>
|
||||
<string>libPasscodeInputFieldNode.a</string>
|
||||
<key>path</key>
|
||||
<string>libPasscodeInputFieldNode.a</string>
|
||||
<key>sourceTree</key>
|
||||
<string>BUILT_PRODUCTS_DIR</string>
|
||||
<key>explicitFileType</key>
|
||||
<string>archive.ar</string>
|
||||
</dict>
|
||||
<key>B401C979C806358400000000</key>
|
||||
<dict>
|
||||
<key>isa</key>
|
||||
<string>PBXGroup</string>
|
||||
<key>name</key>
|
||||
<string>Products</string>
|
||||
<key>sourceTree</key>
|
||||
<string><![CDATA[<group>]]></string>
|
||||
<key>children</key>
|
||||
<array>
|
||||
<string>1DD70E2989A0042800000000</string>
|
||||
</array>
|
||||
</dict>
|
||||
<key>B401C979EFB6AC4600000000</key>
|
||||
<dict>
|
||||
<key>isa</key>
|
||||
<string>PBXGroup</string>
|
||||
<key>name</key>
|
||||
<string>mainGroup</string>
|
||||
<key>sourceTree</key>
|
||||
<string><![CDATA[<group>]]></string>
|
||||
<key>children</key>
|
||||
<array>
|
||||
<string>B401C979B781F65D00000000</string>
|
||||
<string>B401C97968022A5500000000</string>
|
||||
<string>B401C979928346B000000000</string>
|
||||
<string>B401C979C806358400000000</string>
|
||||
</array>
|
||||
</dict>
|
||||
<key>E7A30F040BCAAD5500000000</key>
|
||||
<dict>
|
||||
<key>isa</key>
|
||||
<string>PBXBuildFile</string>
|
||||
<key>fileRef</key>
|
||||
<string>1DD70E290BCAAD5500000000</string>
|
||||
</dict>
|
||||
<key>1870857F0000000000000000</key>
|
||||
<dict>
|
||||
<key>isa</key>
|
||||
<string>PBXSourcesBuildPhase</string>
|
||||
<key>files</key>
|
||||
<array>
|
||||
<string>E7A30F040BCAAD5500000000</string>
|
||||
</array>
|
||||
</dict>
|
||||
<key>E7A30F04FF334B1F00000000</key>
|
||||
<dict>
|
||||
<key>isa</key>
|
||||
<string>PBXBuildFile</string>
|
||||
<key>fileRef</key>
|
||||
<string>1DD70E29FF334B1F00000000</string>
|
||||
</dict>
|
||||
<key>E7A30F04D65BA68200000000</key>
|
||||
<dict>
|
||||
<key>isa</key>
|
||||
<string>PBXBuildFile</string>
|
||||
<key>fileRef</key>
|
||||
<string>1DD70E29D65BA68200000000</string>
|
||||
</dict>
|
||||
<key>FAF5FAC90000000000000000</key>
|
||||
<dict>
|
||||
<key>isa</key>
|
||||
<string>PBXCopyFilesBuildPhase</string>
|
||||
<key>files</key>
|
||||
<array>
|
||||
<string>E7A30F04FF334B1F00000000</string>
|
||||
<string>E7A30F04D65BA68200000000</string>
|
||||
</array>
|
||||
<key>name</key>
|
||||
<string>Fake Swift Dependencies (Copy Files Phase)</string>
|
||||
<key>runOnlyForDeploymentPostprocessing</key>
|
||||
<integer>1</integer>
|
||||
<key>dstSubfolderSpec</key>
|
||||
<integer>16</integer>
|
||||
<key>dstPath</key>
|
||||
<string></string>
|
||||
</dict>
|
||||
<key>4952437303EDA63300000000</key>
|
||||
<dict>
|
||||
<key>isa</key>
|
||||
<string>XCBuildConfiguration</string>
|
||||
<key>name</key>
|
||||
<string>Debug</string>
|
||||
<key>buildSettings</key>
|
||||
<dict>
|
||||
</dict>
|
||||
<key>baseConfigurationReference</key>
|
||||
<string>1DD70E2954723C0500000000</string>
|
||||
</dict>
|
||||
<key>4952437350C7218900000000</key>
|
||||
<dict>
|
||||
<key>isa</key>
|
||||
<string>XCBuildConfiguration</string>
|
||||
<key>name</key>
|
||||
<string>Profile</string>
|
||||
<key>buildSettings</key>
|
||||
<dict>
|
||||
</dict>
|
||||
<key>baseConfigurationReference</key>
|
||||
<string>1DD70E29E5F5E62F00000000</string>
|
||||
</dict>
|
||||
<key>49524373A439BFE700000000</key>
|
||||
<dict>
|
||||
<key>isa</key>
|
||||
<string>XCBuildConfiguration</string>
|
||||
<key>name</key>
|
||||
<string>Release</string>
|
||||
<key>buildSettings</key>
|
||||
<dict>
|
||||
</dict>
|
||||
<key>baseConfigurationReference</key>
|
||||
<string>1DD70E29598C919100000000</string>
|
||||
</dict>
|
||||
<key>218C37090000000000000000</key>
|
||||
<dict>
|
||||
<key>isa</key>
|
||||
<string>XCConfigurationList</string>
|
||||
<key>buildConfigurations</key>
|
||||
<array>
|
||||
<string>4952437303EDA63300000000</string>
|
||||
<string>4952437350C7218900000000</string>
|
||||
<string>49524373A439BFE700000000</string>
|
||||
</array>
|
||||
<key>defaultConfigurationIsVisible</key>
|
||||
<false/>
|
||||
</dict>
|
||||
<key>E66DC04E928346B000000000</key>
|
||||
<dict>
|
||||
<key>isa</key>
|
||||
<string>PBXNativeTarget</string>
|
||||
<key>name</key>
|
||||
<string>PasscodeInputFieldNode</string>
|
||||
<key>productName</key>
|
||||
<string>PasscodeInputFieldNode</string>
|
||||
<key>productReference</key>
|
||||
<string>1DD70E2989A0042800000000</string>
|
||||
<key>productType</key>
|
||||
<string>com.apple.product-type.library.static</string>
|
||||
<key>dependencies</key>
|
||||
<array>
|
||||
</array>
|
||||
<key>buildPhases</key>
|
||||
<array>
|
||||
<string>1870857F0000000000000000</string>
|
||||
<string>FAF5FAC90000000000000000</string>
|
||||
</array>
|
||||
<key>buildConfigurationList</key>
|
||||
<string>218C37090000000000000000</string>
|
||||
</dict>
|
||||
<key>4952437303EDA63300000001</key>
|
||||
<dict>
|
||||
<key>isa</key>
|
||||
<string>XCBuildConfiguration</string>
|
||||
<key>name</key>
|
||||
<string>Debug</string>
|
||||
<key>buildSettings</key>
|
||||
<dict>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>4952437350C7218900000001</key>
|
||||
<dict>
|
||||
<key>isa</key>
|
||||
<string>XCBuildConfiguration</string>
|
||||
<key>name</key>
|
||||
<string>Profile</string>
|
||||
<key>buildSettings</key>
|
||||
<dict>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>49524373A439BFE700000001</key>
|
||||
<dict>
|
||||
<key>isa</key>
|
||||
<string>XCBuildConfiguration</string>
|
||||
<key>name</key>
|
||||
<string>Release</string>
|
||||
<key>buildSettings</key>
|
||||
<dict>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>218C37090000000000000001</key>
|
||||
<dict>
|
||||
<key>isa</key>
|
||||
<string>XCConfigurationList</string>
|
||||
<key>buildConfigurations</key>
|
||||
<array>
|
||||
<string>4952437303EDA63300000001</string>
|
||||
<string>4952437350C7218900000001</string>
|
||||
<string>49524373A439BFE700000001</string>
|
||||
</array>
|
||||
<key>defaultConfigurationIsVisible</key>
|
||||
<false/>
|
||||
</dict>
|
||||
<key>96C84793928346B000000000</key>
|
||||
<dict>
|
||||
<key>isa</key>
|
||||
<string>PBXProject</string>
|
||||
<key>mainGroup</key>
|
||||
<string>B401C979EFB6AC4600000000</string>
|
||||
<key>targets</key>
|
||||
<array>
|
||||
<string>E66DC04E928346B000000000</string>
|
||||
</array>
|
||||
<key>buildConfigurationList</key>
|
||||
<string>218C37090000000000000001</string>
|
||||
<key>compatibilityVersion</key>
|
||||
<string>Xcode 3.2</string>
|
||||
<key>attributes</key>
|
||||
<dict>
|
||||
<key>LastUpgradeCheck</key>
|
||||
<string>9999</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>rootObject</key>
|
||||
<string>96C84793928346B000000000</string>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?><Scheme LastUpgradeVersion="9999" version="1.7"><BuildAction buildImplicitDependencies="YES" parallelizeBuildables="YES"><BuildActionEntries><BuildActionEntry buildForAnalyzing="YES" buildForArchiving="YES" buildForProfiling="YES" buildForRunning="YES" buildForTesting="YES"><BuildableReference BlueprintIdentifier="E66DC04E928346B000000000" BlueprintName="PasscodeInputFieldNode" BuildableIdentifier="primary" BuildableName="libPasscodeInputFieldNode.a" ReferencedContainer="container:PasscodeInputFieldNode.xcodeproj"/></BuildActionEntry></BuildActionEntries></BuildAction><TestAction buildConfiguration="Debug" shouldUseLaunchSchemeArgsEnv="YES"><Testables/></TestAction><LaunchAction buildConfiguration="Debug"/><ProfileAction buildConfiguration="Release"/><AnalyzeAction buildConfiguration="Debug"/><ArchiveAction buildConfiguration="Release" revealArchiveInOrganizer="YES"/></Scheme>
|
||||
@@ -0,0 +1,11 @@
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
//! Project version number for PasscodeInputFieldNode.
|
||||
FOUNDATION_EXPORT double PasscodeInputFieldNodeVersionNumber;
|
||||
|
||||
//! Project version string for PasscodeInputFieldNode.
|
||||
FOUNDATION_EXPORT const unsigned char PasscodeInputFieldNodeVersionString[];
|
||||
|
||||
// In this header, you should import all the public headers of your framework using statements like #import <PasscodeInputFieldNode/PublicHeader.h>
|
||||
|
||||
|
||||
@@ -0,0 +1,382 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import AsyncDisplayKit
|
||||
import Display
|
||||
import SwiftSignalKit
|
||||
|
||||
private let dotDiameter: CGFloat = 13.0
|
||||
private let dotSpacing: CGFloat = 24.0
|
||||
private let fieldHeight: CGFloat = 38.0
|
||||
|
||||
private func generateDotImage(color: UIColor, filled: Bool) -> UIImage? {
|
||||
return generateImage(CGSize(width: dotDiameter, height: dotDiameter), contextGenerator: { size, context in
|
||||
let bounds = CGRect(origin: CGPoint(), size: size)
|
||||
context.clear(bounds)
|
||||
|
||||
if filled {
|
||||
context.setFillColor(color.cgColor)
|
||||
context.fillEllipse(in: bounds)
|
||||
} else {
|
||||
context.setStrokeColor(color.cgColor)
|
||||
context.setLineWidth(1.0)
|
||||
context.strokeEllipse(in: bounds.insetBy(dx: 0.5, dy: 0.5))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private func generateFieldBackgroundImage(backgroundImage: UIImage, backgroundSize: CGSize, frame: CGRect) -> UIImage? {
|
||||
return generateImage(frame.size, contextGenerator: { size, context in
|
||||
let bounds = CGRect(origin: CGPoint(), size: size)
|
||||
context.clear(bounds)
|
||||
|
||||
let relativeFrame = CGRect(x: -frame.minX, y: frame.minY - backgroundSize.height + frame.size.height
|
||||
, width: backgroundSize.width, height: backgroundSize.height)
|
||||
|
||||
let path = UIBezierPath(roundedRect: CGRect(x: 0.0, y: 0.0, width: size.width, height: size.height), cornerRadius: 6.0)
|
||||
context.addPath(path.cgPath)
|
||||
context.clip()
|
||||
|
||||
context.draw(backgroundImage.cgImage!, in: relativeFrame)
|
||||
|
||||
context.setBlendMode(.clear)
|
||||
context.setFillColor(UIColor.clear.cgColor)
|
||||
|
||||
let innerPath = UIBezierPath(roundedRect: CGRect(x: 1.0, y: 1.0, width: size.width - 2.0, height: size.height - 2.0), cornerRadius: 6.0)
|
||||
context.addPath(innerPath.cgPath)
|
||||
context.fillPath()
|
||||
})
|
||||
}
|
||||
|
||||
private let validDigitsSet: CharacterSet = {
|
||||
return CharacterSet(charactersIn: "0".unicodeScalars.first! ... "9".unicodeScalars.first!)
|
||||
}()
|
||||
|
||||
public enum PasscodeEntryFieldType {
|
||||
case digits6
|
||||
case digits4
|
||||
case alphanumeric
|
||||
|
||||
public var maxLength: Int? {
|
||||
switch self {
|
||||
case .digits6:
|
||||
return 6
|
||||
case .digits4:
|
||||
return 4
|
||||
case .alphanumeric:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
public var allowedCharacters: CharacterSet? {
|
||||
switch self {
|
||||
case .digits6, .digits4:
|
||||
return validDigitsSet
|
||||
case .alphanumeric:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
public var keyboardType: UIKeyboardType {
|
||||
switch self {
|
||||
case .digits6, .digits4:
|
||||
if #available(iOS 10.0, *) {
|
||||
return .asciiCapableNumberPad
|
||||
} else {
|
||||
return .numberPad
|
||||
}
|
||||
case .alphanumeric:
|
||||
return .default
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class PasscodeEntryInputView: UIView {
|
||||
|
||||
}
|
||||
|
||||
private class PasscodeEntryDotNode: ASImageNode {
|
||||
private let regularImage: UIImage
|
||||
private let filledImage: UIImage
|
||||
private var currentImage: UIImage
|
||||
|
||||
init(color: UIColor) {
|
||||
self.regularImage = generateDotImage(color: color, filled: false)!
|
||||
self.filledImage = generateDotImage(color: color, filled: true)!
|
||||
self.currentImage = self.regularImage
|
||||
|
||||
super.init()
|
||||
|
||||
self.image = self.currentImage
|
||||
}
|
||||
|
||||
func updateState(filled: Bool, animated: Bool = false, delay: Double = 0.0) {
|
||||
let image = filled ? self.filledImage : self.regularImage
|
||||
if self.currentImage !== image {
|
||||
let currentContents = self.layer.contents
|
||||
self.layer.removeAnimation(forKey: "contents")
|
||||
if let currentContents = currentContents, animated {
|
||||
self.layer.animate(from: currentContents as AnyObject, to: image.cgImage!, keyPath: "contents", timingFunction: CAMediaTimingFunctionName.easeOut.rawValue, duration: image === self.regularImage ? 0.25 : 0.05, delay: delay, removeOnCompletion: false, completion: { finished in
|
||||
if finished {
|
||||
self.image = image
|
||||
}
|
||||
})
|
||||
} else {
|
||||
self.image = image
|
||||
}
|
||||
self.currentImage = image
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public final class PasscodeInputFieldNode: ASDisplayNode, UITextFieldDelegate {
|
||||
private var background: (UIImage, CGSize)?
|
||||
private var color: UIColor
|
||||
private var accentColor: UIColor
|
||||
private var fieldType: PasscodeEntryFieldType
|
||||
private let useCustomNumpad: Bool
|
||||
|
||||
private let textFieldNode: TextFieldNode
|
||||
private let borderNode: ASImageNode
|
||||
private let dotNodes: [PasscodeEntryDotNode]
|
||||
|
||||
private var validLayout: (ContainerViewLayout, CGFloat)?
|
||||
|
||||
public var complete: ((String) -> Void)?
|
||||
|
||||
public var text: String {
|
||||
return self.textFieldNode.textField.text ?? ""
|
||||
}
|
||||
|
||||
public var keyboardAppearance: UIKeyboardAppearance {
|
||||
didSet {
|
||||
self.textFieldNode.textField.keyboardAppearance = self.keyboardAppearance
|
||||
}
|
||||
}
|
||||
|
||||
public init(color: UIColor, accentColor: UIColor, fieldType: PasscodeEntryFieldType, keyboardAppearance: UIKeyboardAppearance, useCustomNumpad: Bool = false) {
|
||||
self.color = color
|
||||
self.accentColor = accentColor
|
||||
self.fieldType = fieldType
|
||||
self.keyboardAppearance = keyboardAppearance
|
||||
self.useCustomNumpad = useCustomNumpad
|
||||
|
||||
self.textFieldNode = TextFieldNode()
|
||||
self.borderNode = ASImageNode()
|
||||
self.dotNodes = (0 ..< 6).map { _ in PasscodeEntryDotNode(color: color) }
|
||||
|
||||
super.init()
|
||||
|
||||
self.isUserInteractionEnabled = false
|
||||
|
||||
for node in self.dotNodes {
|
||||
self.addSubnode(node)
|
||||
}
|
||||
self.addSubnode(self.textFieldNode)
|
||||
self.addSubnode(self.borderNode)
|
||||
}
|
||||
|
||||
override public func didLoad() {
|
||||
super.didLoad()
|
||||
|
||||
self.textFieldNode.textField.isSecureTextEntry = true
|
||||
self.textFieldNode.textField.textColor = self.color
|
||||
self.textFieldNode.textField.delegate = self
|
||||
self.textFieldNode.textField.returnKeyType = .done
|
||||
self.textFieldNode.textField.tintColor = self.accentColor
|
||||
self.textFieldNode.textField.keyboardAppearance = self.keyboardAppearance
|
||||
self.textFieldNode.textField.keyboardType = self.fieldType.keyboardType
|
||||
self.textFieldNode.textField.tintColor = self.accentColor
|
||||
|
||||
if self.useCustomNumpad {
|
||||
switch self.fieldType {
|
||||
case .digits6, .digits4:
|
||||
self.textFieldNode.textField.inputView = PasscodeEntryInputView()
|
||||
case .alphanumeric:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func updateFieldType(_ fieldType: PasscodeEntryFieldType, animated: Bool) {
|
||||
self.fieldType = fieldType
|
||||
|
||||
self.textFieldNode.textField.keyboardType = self.fieldType.keyboardType
|
||||
|
||||
if let (layout, topOffset) = self.validLayout {
|
||||
let _ = self.updateLayout(layout: layout, topOffset: topOffset, transition: animated ? .animated(duration: 0.25, curve: .easeInOut) : .immediate)
|
||||
}
|
||||
}
|
||||
|
||||
func updateBackground(_ image: UIImage, size: CGSize) {
|
||||
self.background = (image, size)
|
||||
if let (layout, topOffset) = self.validLayout {
|
||||
let _ = self.updateLayout(layout: layout, topOffset: topOffset, transition: .immediate)
|
||||
}
|
||||
}
|
||||
|
||||
public func activateInput() {
|
||||
self.textFieldNode.textField.becomeFirstResponder()
|
||||
}
|
||||
|
||||
func animateIn() {
|
||||
switch self.fieldType {
|
||||
case .digits6, .digits4:
|
||||
for node in self.dotNodes {
|
||||
node.layer.animateScale(from: 0.0001, to: 1.0, duration: 0.25, timingFunction: CAMediaTimingFunctionName.easeOut.rawValue)
|
||||
}
|
||||
case .alphanumeric:
|
||||
self.textFieldNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25, timingFunction: CAMediaTimingFunctionName.easeOut.rawValue)
|
||||
self.borderNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25, timingFunction: CAMediaTimingFunctionName.easeOut.rawValue)
|
||||
}
|
||||
}
|
||||
|
||||
func animateSuccess() {
|
||||
switch self.fieldType {
|
||||
case .digits6, .digits4:
|
||||
var delay: Double = 0.0
|
||||
for node in self.dotNodes {
|
||||
node.updateState(filled: true, animated: true, delay: delay)
|
||||
delay += 0.01
|
||||
}
|
||||
case .alphanumeric:
|
||||
if (self.textFieldNode.textField.text ?? "").isEmpty {
|
||||
self.textFieldNode.textField.text = "passwordpassword"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func reset(animated: Bool = true) {
|
||||
var delay: Double = 0.0
|
||||
for node in self.dotNodes.reversed() {
|
||||
if node.alpha < 1.0 {
|
||||
continue
|
||||
}
|
||||
|
||||
node.updateState(filled: false, animated: animated, delay: delay)
|
||||
delay += 0.05
|
||||
}
|
||||
self.textFieldNode.textField.text = ""
|
||||
}
|
||||
|
||||
func append(_ string: String) {
|
||||
var text = (self.textFieldNode.textField.text ?? "") + string
|
||||
let maxLength = self.fieldType.maxLength
|
||||
if let maxLength = maxLength, text.count > maxLength {
|
||||
return
|
||||
}
|
||||
self.textFieldNode.textField.text = text
|
||||
|
||||
text = self.textFieldNode.textField.text ?? "" + string
|
||||
self.updateDots(count: text.count, animated: false)
|
||||
|
||||
if let maxLength = maxLength, text.count == maxLength {
|
||||
Queue.mainQueue().after(0.2) {
|
||||
self.complete?(text)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func delete() {
|
||||
var text = self.textFieldNode.textField.text ?? ""
|
||||
guard !text.isEmpty else {
|
||||
return
|
||||
}
|
||||
text = String(text[text.startIndex ..< text.index(text.endIndex, offsetBy: -1)])
|
||||
self.textFieldNode.textField.text = text
|
||||
self.updateDots(count: text.count, animated: true)
|
||||
}
|
||||
|
||||
func updateDots(count: Int, animated: Bool) {
|
||||
var i = -1
|
||||
for node in self.dotNodes {
|
||||
if node.alpha < 1.0 {
|
||||
continue
|
||||
}
|
||||
i += 1
|
||||
node.updateState(filled: i < count, animated: animated)
|
||||
}
|
||||
}
|
||||
|
||||
public func update(fieldType: PasscodeEntryFieldType) {
|
||||
if fieldType != self.fieldType {
|
||||
self.textFieldNode.textField.text = ""
|
||||
}
|
||||
self.fieldType = fieldType
|
||||
if let (layout, topOffset) = self.validLayout {
|
||||
let _ = self.updateLayout(layout: layout, topOffset: topOffset, transition: .immediate)
|
||||
}
|
||||
}
|
||||
|
||||
public func updateLayout(layout: ContainerViewLayout, topOffset: CGFloat, transition: ContainedViewLayoutTransition) -> CGRect {
|
||||
self.validLayout = (layout, topOffset)
|
||||
|
||||
let fieldAlpha: CGFloat
|
||||
switch self.fieldType {
|
||||
case .digits6, .digits4:
|
||||
fieldAlpha = 0.0
|
||||
case .alphanumeric:
|
||||
fieldAlpha = 1.0
|
||||
}
|
||||
|
||||
transition.updateAlpha(node: self.textFieldNode, alpha: fieldAlpha)
|
||||
transition.updateAlpha(node: self.borderNode, alpha: fieldAlpha)
|
||||
|
||||
let origin = CGPoint(x: floor((layout.size.width - dotDiameter * 6 - dotSpacing * 5) / 2.0), y: topOffset)
|
||||
for i in 0 ..< self.dotNodes.count {
|
||||
let node = self.dotNodes[i]
|
||||
let dotAlpha: CGFloat
|
||||
switch self.fieldType {
|
||||
case .digits6:
|
||||
dotAlpha = 1.0
|
||||
case .digits4:
|
||||
dotAlpha = (i > 0 && i < self.dotNodes.count - 1) ? 1.0 : 0.0
|
||||
case .alphanumeric:
|
||||
dotAlpha = 0.0
|
||||
}
|
||||
transition.updateAlpha(node: node, alpha: dotAlpha)
|
||||
|
||||
let dotFrame = CGRect(x: origin.x + CGFloat(i) * (dotDiameter + dotSpacing), y: origin.y, width: dotDiameter, height: dotDiameter)
|
||||
transition.updateFrame(node: node, frame: dotFrame)
|
||||
}
|
||||
|
||||
var inset: CGFloat = 50.0
|
||||
if !self.useCustomNumpad {
|
||||
inset = 16.0
|
||||
}
|
||||
let fieldFrame = CGRect(x: inset, y: origin.y, width: layout.size.width - inset * 2.0, height: fieldHeight)
|
||||
transition.updateFrame(node: self.borderNode, frame: fieldFrame)
|
||||
transition.updateFrame(node: self.textFieldNode, frame: fieldFrame.insetBy(dx: 13.0, dy: 0.0))
|
||||
if let (backgroundImage, backgroundSize) = self.background {
|
||||
self.borderNode.image = generateFieldBackgroundImage(backgroundImage: backgroundImage, backgroundSize: backgroundSize, frame: fieldFrame)
|
||||
}
|
||||
|
||||
return fieldFrame
|
||||
}
|
||||
|
||||
public func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
|
||||
let currentText = textField.text ?? ""
|
||||
let text = (currentText as NSString).replacingCharacters(in: range, with: string)
|
||||
if let maxLength = self.fieldType.maxLength, text.count > maxLength {
|
||||
return false
|
||||
}
|
||||
if let allowedCharacters = self.fieldType.allowedCharacters, let _ = text.rangeOfCharacter(from: allowedCharacters.inverted) {
|
||||
return false
|
||||
}
|
||||
self.updateDots(count: text.count, animated: text.count < currentText.count)
|
||||
|
||||
if string == "\n" {
|
||||
Queue.mainQueue().after(0.2) {
|
||||
self.complete?(currentText)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
if let maxLength = self.fieldType.maxLength, text.count == maxLength {
|
||||
Queue.mainQueue().after(0.2) {
|
||||
self.complete?(text)
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user