mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
495 lines
24 KiB
Swift
495 lines
24 KiB
Swift
import Foundation
|
|
import UIKit
|
|
import SwiftSignalKit
|
|
import AsyncDisplayKit
|
|
import Display
|
|
import TelegramPresentationData
|
|
import ActivityIndicator
|
|
import AppBundle
|
|
|
|
private func generateLoupeIcon(color: UIColor) -> UIImage? {
|
|
return generateTintedImage(image: UIImage(bundleImageName: "Components/Search Bar/Loupe"), color: color)
|
|
}
|
|
|
|
private func generateClearIcon(color: UIColor) -> UIImage? {
|
|
return generateTintedImage(image: UIImage(bundleImageName: "Components/Search Bar/Clear"), color: color)
|
|
}
|
|
|
|
private func generateBackground(backgroundColor: UIColor, foregroundColor: UIColor) -> UIImage? {
|
|
let diameter: CGFloat = 10.0
|
|
return generateImage(CGSize(width: diameter, height: diameter), contextGenerator: { size, context in
|
|
context.setFillColor(backgroundColor.cgColor)
|
|
context.fill(CGRect(origin: CGPoint(), size: size))
|
|
context.setFillColor(foregroundColor.cgColor)
|
|
context.fillEllipse(in: CGRect(origin: CGPoint(), size: size))
|
|
}, opaque: true)?.stretchableImage(withLeftCapWidth: Int(diameter / 2.0), topCapHeight: Int(diameter / 2.0))
|
|
}
|
|
|
|
private class PaneSearchBarTextField: UITextField {
|
|
public var didDeleteBackwardWhileEmpty: (() -> Void)?
|
|
|
|
let placeholderLabel: ImmediateTextNode
|
|
var placeholderString: NSAttributedString? {
|
|
didSet {
|
|
self.placeholderLabel.attributedText = self.placeholderString
|
|
}
|
|
}
|
|
|
|
let prefixLabel: ASTextNode
|
|
var prefixString: NSAttributedString? {
|
|
didSet {
|
|
self.prefixLabel.attributedText = self.prefixString
|
|
}
|
|
}
|
|
|
|
override init(frame: CGRect) {
|
|
self.placeholderLabel = ImmediateTextNode()
|
|
self.placeholderLabel.isUserInteractionEnabled = false
|
|
self.placeholderLabel.displaysAsynchronously = false
|
|
self.placeholderLabel.maximumNumberOfLines = 1
|
|
|
|
self.prefixLabel = ASTextNode()
|
|
self.prefixLabel.isUserInteractionEnabled = false
|
|
self.prefixLabel.displaysAsynchronously = false
|
|
|
|
super.init(frame: frame)
|
|
|
|
self.addSubnode(self.placeholderLabel)
|
|
self.addSubnode(self.prefixLabel)
|
|
}
|
|
|
|
required init?(coder aDecoder: NSCoder) {
|
|
fatalError("init(coder:) has not been implemented")
|
|
}
|
|
|
|
override var keyboardAppearance: UIKeyboardAppearance {
|
|
get {
|
|
return super.keyboardAppearance
|
|
}
|
|
set {
|
|
let resigning = self.isFirstResponder
|
|
if resigning {
|
|
self.resignFirstResponder()
|
|
}
|
|
super.keyboardAppearance = newValue
|
|
if resigning {
|
|
self.becomeFirstResponder()
|
|
}
|
|
}
|
|
}
|
|
|
|
override func textRect(forBounds bounds: CGRect) -> CGRect {
|
|
if bounds.size.width.isZero {
|
|
return CGRect(origin: CGPoint(), size: CGSize())
|
|
}
|
|
var rect = bounds.insetBy(dx: 4.0, dy: 4.0)
|
|
|
|
let prefixSize = self.prefixLabel.measure(bounds.size)
|
|
if !prefixSize.width.isZero {
|
|
let prefixOffset = prefixSize.width
|
|
rect.origin.x += prefixOffset
|
|
rect.size.width -= prefixOffset
|
|
}
|
|
return rect
|
|
}
|
|
|
|
override func editingRect(forBounds bounds: CGRect) -> CGRect {
|
|
return self.textRect(forBounds: bounds)
|
|
}
|
|
|
|
override func layoutSubviews() {
|
|
super.layoutSubviews()
|
|
|
|
let bounds = self.bounds
|
|
if bounds.size.width.isZero {
|
|
return
|
|
}
|
|
|
|
let constrainedSize = self.textRect(forBounds: self.bounds).size
|
|
let labelSize = self.placeholderLabel.updateLayout(constrainedSize)
|
|
self.placeholderLabel.frame = CGRect(origin: CGPoint(x: self.textRect(forBounds: bounds).minX, y: self.textRect(forBounds: bounds).minY + 4.0), size: labelSize)
|
|
|
|
let prefixSize = self.prefixLabel.measure(constrainedSize)
|
|
let prefixBounds = bounds.insetBy(dx: 4.0, dy: 4.0)
|
|
self.prefixLabel.frame = CGRect(origin: CGPoint(x: prefixBounds.minX, y: prefixBounds.minY + 1.0), size: prefixSize)
|
|
}
|
|
|
|
override func deleteBackward() {
|
|
if self.text == nil || self.text!.isEmpty {
|
|
self.didDeleteBackwardWhileEmpty?()
|
|
}
|
|
super.deleteBackward()
|
|
}
|
|
}
|
|
|
|
class PaneSearchBarNode: ASDisplayNode, UITextFieldDelegate {
|
|
var cancel: (() -> Void)?
|
|
var textUpdated: ((String, String) -> Void)?
|
|
var clearPrefix: (() -> Void)?
|
|
|
|
private let backgroundNode: ASDisplayNode
|
|
private let separatorNode: ASDisplayNode
|
|
private let textBackgroundNode: ASImageNode
|
|
private var activityIndicator: ActivityIndicator?
|
|
private let iconNode: ASImageNode
|
|
private let textField: PaneSearchBarTextField
|
|
private let clearButton: HighlightableButtonNode
|
|
private let cancelButton: ASButtonNode
|
|
|
|
var placeholderString: NSAttributedString? {
|
|
get {
|
|
return self.textField.placeholderString
|
|
} set(value) {
|
|
self.textField.placeholderString = value
|
|
}
|
|
}
|
|
|
|
var prefixString: NSAttributedString? {
|
|
get {
|
|
return self.textField.prefixString
|
|
} set(value) {
|
|
let previous = self.prefixString
|
|
let updated: Bool
|
|
if let previous = previous, let value = value {
|
|
updated = !previous.isEqual(to: value)
|
|
} else {
|
|
updated = (previous != nil) != (value != nil)
|
|
}
|
|
if updated {
|
|
self.textField.prefixString = value
|
|
self.textField.setNeedsLayout()
|
|
self.updateIsEmpty()
|
|
}
|
|
}
|
|
}
|
|
|
|
var text: String {
|
|
get {
|
|
return self.textField.text ?? ""
|
|
} set(value) {
|
|
if self.textField.text ?? "" != value {
|
|
self.textField.text = value
|
|
self.textFieldDidChange(self.textField)
|
|
}
|
|
}
|
|
}
|
|
|
|
var activity: Bool = false {
|
|
didSet {
|
|
if self.activity != oldValue {
|
|
if self.activity {
|
|
if self.activityIndicator == nil, let theme = self.theme {
|
|
let activityIndicator = ActivityIndicator(type: .custom(theme.chat.inputMediaPanel.stickersSearchControlColor, 13.0, 1.0, false))
|
|
self.activityIndicator = activityIndicator
|
|
self.addSubnode(activityIndicator)
|
|
if let (boundingSize, leftInset, rightInset) = self.validLayout {
|
|
self.updateLayout(boundingSize: boundingSize, leftInset: leftInset, rightInset: rightInset, transition: .immediate)
|
|
}
|
|
}
|
|
} else if let activityIndicator = self.activityIndicator {
|
|
self.activityIndicator = nil
|
|
activityIndicator.removeFromSupernode()
|
|
}
|
|
self.iconNode.isHidden = self.activity
|
|
}
|
|
}
|
|
}
|
|
|
|
private var validLayout: (CGSize, CGFloat, CGFloat)?
|
|
private var theme: PresentationTheme?
|
|
|
|
override init() {
|
|
self.backgroundNode = ASDisplayNode()
|
|
self.backgroundNode.isLayerBacked = true
|
|
|
|
self.separatorNode = ASDisplayNode()
|
|
self.separatorNode.isLayerBacked = true
|
|
|
|
self.textBackgroundNode = ASImageNode()
|
|
self.textBackgroundNode.isLayerBacked = false
|
|
self.textBackgroundNode.displaysAsynchronously = false
|
|
self.textBackgroundNode.displayWithoutProcessing = true
|
|
|
|
self.iconNode = ASImageNode()
|
|
self.iconNode.isUserInteractionEnabled = false
|
|
self.iconNode.displaysAsynchronously = false
|
|
self.iconNode.displayWithoutProcessing = true
|
|
|
|
self.textField = PaneSearchBarTextField()
|
|
self.textField.accessibilityTraits = .searchField
|
|
self.textField.autocorrectionType = .no
|
|
self.textField.returnKeyType = .search
|
|
self.textField.font = Font.regular(17.0)
|
|
|
|
self.clearButton = HighlightableButtonNode()
|
|
self.clearButton.imageNode.displaysAsynchronously = false
|
|
self.clearButton.imageNode.displayWithoutProcessing = true
|
|
self.clearButton.displaysAsynchronously = false
|
|
self.clearButton.isHidden = true
|
|
|
|
self.cancelButton = ASButtonNode()
|
|
self.cancelButton.hitTestSlop = UIEdgeInsets(top: -8.0, left: -8.0, bottom: -8.0, right: -8.0)
|
|
self.cancelButton.displaysAsynchronously = false
|
|
|
|
super.init()
|
|
|
|
self.addSubnode(self.backgroundNode)
|
|
self.addSubnode(self.separatorNode)
|
|
|
|
self.addSubnode(self.textBackgroundNode)
|
|
self.view.addSubview(self.textField)
|
|
self.addSubnode(self.iconNode)
|
|
self.addSubnode(self.clearButton)
|
|
self.addSubnode(self.cancelButton)
|
|
|
|
self.textField.delegate = self
|
|
self.textField.addTarget(self, action: #selector(self.textFieldDidChange(_:)), for: .editingChanged)
|
|
|
|
self.textField.didDeleteBackwardWhileEmpty = { [weak self] in
|
|
self?.clearPressed()
|
|
}
|
|
|
|
self.cancelButton.addTarget(self, action: #selector(self.cancelPressed), forControlEvents: .touchUpInside)
|
|
self.clearButton.addTarget(self, action: #selector(self.clearPressed), forControlEvents: .touchUpInside)
|
|
}
|
|
|
|
func updateThemeAndStrings(theme: PresentationTheme, strings: PresentationStrings) {
|
|
self.theme = theme
|
|
|
|
if let activityIndicator = self.activityIndicator {
|
|
activityIndicator.type = .custom(theme.chat.inputMediaPanel.stickersSearchControlColor, 13.0, 1.0, false)
|
|
}
|
|
self.separatorNode.backgroundColor = theme.chat.inputMediaPanel.panelSeparatorColor
|
|
self.textBackgroundNode.image = generateStretchableFilledCircleImage(diameter: 36.0, color: theme.chat.inputMediaPanel.stickersSearchBackgroundColor)
|
|
self.textField.textColor = theme.chat.inputMediaPanel.stickersSearchPrimaryColor
|
|
self.iconNode.image = generateLoupeIcon(color: theme.chat.inputMediaPanel.stickersSearchControlColor)
|
|
self.clearButton.setImage(generateClearIcon(color: theme.chat.inputMediaPanel.stickersSearchControlColor), for: [])
|
|
self.cancelButton.setAttributedTitle(NSAttributedString(string: strings.Common_Cancel, font: Font.regular(17.0), textColor: theme.chat.inputPanel.panelControlAccentColor), for: [])
|
|
self.textField.keyboardAppearance = theme.rootController.keyboardColor.keyboardAppearance
|
|
self.textField.tintColor = theme.list.itemAccentColor
|
|
|
|
if let (boundingSize, leftInset, rightInset) = self.validLayout {
|
|
self.updateLayout(boundingSize: boundingSize, leftInset: leftInset, rightInset: rightInset, transition: .immediate)
|
|
}
|
|
}
|
|
|
|
func updateLayout(boundingSize: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) {
|
|
self.validLayout = (boundingSize, leftInset, rightInset)
|
|
|
|
self.backgroundNode.frame = self.bounds
|
|
transition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: self.bounds.size.height), size: CGSize(width: self.bounds.size.width, height: UIScreenPixel)))
|
|
|
|
let verticalOffset: CGFloat = -20.0
|
|
|
|
let contentFrame = CGRect(origin: CGPoint(x: leftInset, y: 0.0), size: CGSize(width: boundingSize.width - leftInset - rightInset, height: boundingSize.height))
|
|
|
|
let cancelButtonSize = self.cancelButton.measure(CGSize(width: 100.0, height: CGFloat.infinity))
|
|
transition.updateFrame(node: self.cancelButton, frame: CGRect(origin: CGPoint(x: contentFrame.maxX - 8.0 - cancelButtonSize.width, y: verticalOffset + 34.0), size: cancelButtonSize))
|
|
|
|
let textBackgroundFrame = CGRect(origin: CGPoint(x: contentFrame.minX + 8.0, y: verticalOffset + 28.0), size: CGSize(width: contentFrame.width - 16.0 - cancelButtonSize.width - 11.0, height: 36.0))
|
|
transition.updateFrame(node: self.textBackgroundNode, frame: textBackgroundFrame)
|
|
|
|
let textFrame = CGRect(origin: CGPoint(x: textBackgroundFrame.minX + 27.0, y: textBackgroundFrame.minY), size: CGSize(width: max(1.0, textBackgroundFrame.size.width - 27.0 - 20.0), height: textBackgroundFrame.size.height))
|
|
|
|
if let iconImage = self.iconNode.image {
|
|
let iconSize = iconImage.size
|
|
transition.updateFrame(node: self.iconNode, frame: CGRect(origin: CGPoint(x: textBackgroundFrame.minX + 11.0, y: textBackgroundFrame.minY + floor((textBackgroundFrame.size.height - iconSize.height) / 2.0)), size: iconSize))
|
|
}
|
|
|
|
if let activityIndicator = self.activityIndicator {
|
|
let indicatorSize = activityIndicator.measure(CGSize(width: 32.0, height: 32.0))
|
|
transition.updateFrame(node: activityIndicator, frame: CGRect(origin: CGPoint(x: textBackgroundFrame.minX + 11.0, y: textBackgroundFrame.minY + floor((textBackgroundFrame.size.height - indicatorSize.height) / 2.0)), size: indicatorSize))
|
|
}
|
|
|
|
let clearSize = self.clearButton.measure(CGSize(width: 100.0, height: 100.0))
|
|
transition.updateFrame(node: self.clearButton, frame: CGRect(origin: CGPoint(x: textBackgroundFrame.maxX - 8.0 - clearSize.width, y: textBackgroundFrame.minY + floor((textBackgroundFrame.size.height - clearSize.height) / 2.0)), size: clearSize))
|
|
|
|
self.textField.frame = textFrame
|
|
self.textField.layoutSubviews()
|
|
}
|
|
|
|
@objc private func tapGesture(_ recognizer: UITapGestureRecognizer) {
|
|
if case .ended = recognizer.state {
|
|
if let cancel = self.cancel {
|
|
cancel()
|
|
}
|
|
}
|
|
}
|
|
|
|
func activate() {
|
|
self.textField.becomeFirstResponder()
|
|
}
|
|
|
|
func animateIn(from node: PaneSearchBarPlaceholderNode, duration: Double, timingFunction: String, completion: @escaping () -> Void) {
|
|
let initialTextBackgroundFrame = node.view.convert(node.backgroundNode.frame, to: self.view)
|
|
|
|
var backgroundCompleted = false
|
|
var separatorCompleted = false
|
|
var textBackgroundCompleted = false
|
|
let intermediateCompletion: () -> Void = {
|
|
if backgroundCompleted && separatorCompleted && textBackgroundCompleted {
|
|
completion()
|
|
}
|
|
}
|
|
|
|
let initialBackgroundFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: self.bounds.size.width, height: max(0.0, initialTextBackgroundFrame.maxY + 8.0)))
|
|
if let fromBackgroundColor = node.backgroundColor, let toBackgroundColor = self.backgroundNode.backgroundColor {
|
|
self.backgroundNode.layer.animate(from: fromBackgroundColor.cgColor, to: toBackgroundColor.cgColor, keyPath: "backgroundColor", timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, duration: duration * 0.7)
|
|
} else {
|
|
self.backgroundNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: duration)
|
|
}
|
|
self.backgroundNode.layer.animateFrame(from: initialBackgroundFrame, to: self.backgroundNode.frame, duration: duration, timingFunction: timingFunction, completion: { _ in
|
|
backgroundCompleted = true
|
|
intermediateCompletion()
|
|
})
|
|
|
|
let initialSeparatorFrame = CGRect(origin: CGPoint(x: 0.0, y: max(0.0, initialTextBackgroundFrame.maxY + 8.0)), size: CGSize(width: self.bounds.size.width, height: UIScreenPixel))
|
|
self.separatorNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: duration)
|
|
self.separatorNode.layer.animateFrame(from: initialSeparatorFrame, to: self.separatorNode.frame, duration: duration, timingFunction: timingFunction, completion: { _ in
|
|
separatorCompleted = true
|
|
intermediateCompletion()
|
|
})
|
|
|
|
self.textBackgroundNode.layer.animateFrame(from: initialTextBackgroundFrame, to: self.textBackgroundNode.frame, duration: duration, timingFunction: timingFunction, completion: { _ in
|
|
textBackgroundCompleted = true
|
|
intermediateCompletion()
|
|
})
|
|
|
|
let labelFrame = self.textField.placeholderLabel.frame
|
|
let initialLabelNodeFrame = CGRect(origin: node.labelNode.view.convert(node.labelNode.bounds, to: self.textField.superview).origin, size: labelFrame.size)
|
|
self.textField.layer.animateFrame(from: CGRect(origin: initialLabelNodeFrame.origin.offsetBy(dx: -labelFrame.minX, dy: -labelFrame.minY), size: self.textField.frame.size), to: self.textField.frame, duration: duration, timingFunction: timingFunction)
|
|
|
|
let iconFrame = self.iconNode.frame
|
|
let initialIconFrame = CGRect(origin: node.iconNode.view.convert(node.iconNode.bounds, to: self.iconNode.view.superview).origin, size: iconFrame.size)
|
|
self.iconNode.layer.animateFrame(from: initialIconFrame, to: self.iconNode.frame, duration: duration, timingFunction: timingFunction)
|
|
|
|
let cancelButtonFrame = self.cancelButton.frame
|
|
self.cancelButton.layer.animatePosition(from: CGPoint(x: self.bounds.size.width + cancelButtonFrame.size.width / 2.0, y: initialTextBackgroundFrame.minY + 2.0 + cancelButtonFrame.size.height / 2.0), to: self.cancelButton.layer.position, duration: duration, timingFunction: timingFunction)
|
|
node.isHidden = true
|
|
}
|
|
|
|
func deactivate(clear: Bool = true) {
|
|
self.textField.resignFirstResponder()
|
|
if clear {
|
|
self.textField.text = nil
|
|
self.textField.placeholderLabel.isHidden = false
|
|
}
|
|
}
|
|
|
|
func transitionOut(to node: PaneSearchBarPlaceholderNode, transition: ContainedViewLayoutTransition, completion: @escaping () -> Void) {
|
|
let targetTextBackgroundFrame = node.view.convert(node.backgroundNode.view.frame, to: self.view)
|
|
|
|
let duration: Double = 0.5
|
|
let timingFunction = kCAMediaTimingFunctionSpring
|
|
|
|
node.isHidden = true
|
|
self.clearButton.isHidden = true
|
|
self.textField.text = ""
|
|
|
|
var backgroundCompleted = false
|
|
var separatorCompleted = false
|
|
var textBackgroundCompleted = false
|
|
let intermediateCompletion: () -> Void = { [weak node] in
|
|
if backgroundCompleted && separatorCompleted && textBackgroundCompleted {
|
|
completion()
|
|
node?.isHidden = false
|
|
}
|
|
}
|
|
|
|
let targetBackgroundFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: self.bounds.size.width, height: max(0.0, targetTextBackgroundFrame.maxY + 8.0)))
|
|
if let toBackgroundColor = node.backgroundColor, let fromBackgroundColor = self.backgroundNode.backgroundColor {
|
|
self.backgroundNode.layer.animate(from: fromBackgroundColor.cgColor, to: toBackgroundColor.cgColor, keyPath: "backgroundColor", timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, duration: duration * 0.5, removeOnCompletion: false)
|
|
} else {
|
|
self.backgroundNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: duration / 2.0, removeOnCompletion: false)
|
|
}
|
|
self.backgroundNode.layer.animateFrame(from: self.backgroundNode.frame, to: targetBackgroundFrame, duration: duration, timingFunction: timingFunction, removeOnCompletion: false, completion: { _ in
|
|
backgroundCompleted = true
|
|
intermediateCompletion()
|
|
})
|
|
|
|
let targetSeparatorFrame = CGRect(origin: CGPoint(x: 0.0, y: max(0.0, targetTextBackgroundFrame.maxY + 8.0)), size: CGSize(width: self.bounds.size.width, height: UIScreenPixel))
|
|
self.separatorNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: duration / 2.0, removeOnCompletion: false)
|
|
self.separatorNode.layer.animateFrame(from: self.separatorNode.frame, to: targetSeparatorFrame, duration: duration, timingFunction: timingFunction, removeOnCompletion: false, completion: { _ in
|
|
separatorCompleted = true
|
|
intermediateCompletion()
|
|
})
|
|
|
|
self.textBackgroundNode.layer.animateFrame(from: self.textBackgroundNode.frame, to: targetTextBackgroundFrame, duration: duration, timingFunction: timingFunction, removeOnCompletion: false, completion: { _ in
|
|
textBackgroundCompleted = true
|
|
intermediateCompletion()
|
|
})
|
|
|
|
let transitionBackgroundNode = ASImageNode()
|
|
transitionBackgroundNode.isLayerBacked = true
|
|
transitionBackgroundNode.displaysAsynchronously = false
|
|
transitionBackgroundNode.displayWithoutProcessing = true
|
|
transitionBackgroundNode.image = node.backgroundNode.image
|
|
self.insertSubnode(transitionBackgroundNode, aboveSubnode: self.textBackgroundNode)
|
|
transitionBackgroundNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: duration / 2.0, removeOnCompletion: false)
|
|
transitionBackgroundNode.layer.animateFrame(from: self.textBackgroundNode.frame, to: targetTextBackgroundFrame, duration: duration, timingFunction: timingFunction, removeOnCompletion: false)
|
|
|
|
let textFieldFrame = self.textField.frame
|
|
let targetLabelNodeFrame = CGRect(origin: node.labelNode.view.convert(node.labelNode.bounds, to: self.textField.superview).origin, size: textFieldFrame.size)
|
|
self.textField.layer.animateFrame(from: self.textField.frame, to: CGRect(origin: targetLabelNodeFrame.origin.offsetBy(dx: -self.textField.placeholderLabel.frame.minX, dy: -self.textField.placeholderLabel.frame.minY), size: self.textField.frame.size), duration: duration, timingFunction: timingFunction, removeOnCompletion: false)
|
|
if let snapshot = node.labelNode.layer.snapshotContentTree() {
|
|
snapshot.frame = CGRect(origin: self.textField.placeholderLabel.frame.origin, size: node.labelNode.frame.size)
|
|
self.textField.layer.addSublayer(snapshot)
|
|
snapshot.animateAlpha(from: 0.0, to: 1.0, duration: duration * 2.0 / 3.0, timingFunction: CAMediaTimingFunctionName.linear.rawValue)
|
|
self.textField.placeholderLabel.layer.animateAlpha(from: 1.0, to: 0.0, duration: duration * 3.0 / 2.0, timingFunction: CAMediaTimingFunctionName.linear.rawValue, removeOnCompletion: false)
|
|
|
|
}
|
|
|
|
let iconFrame = self.iconNode.frame
|
|
let targetIconFrame = CGRect(origin: node.iconNode.view.convert(node.iconNode.bounds, to: self.iconNode.view.superview).origin, size: iconFrame.size)
|
|
self.iconNode.image = node.iconNode.image
|
|
self.iconNode.layer.animateFrame(from: self.iconNode.frame, to: targetIconFrame, duration: duration, timingFunction: timingFunction, removeOnCompletion: false)
|
|
|
|
let cancelButtonFrame = self.cancelButton.frame
|
|
self.cancelButton.layer.animatePosition(from: self.cancelButton.layer.position, to: CGPoint(x: self.bounds.size.width + cancelButtonFrame.size.width / 2.0, y: targetTextBackgroundFrame.minY + 2.0 + cancelButtonFrame.size.height / 2.0), duration: duration, timingFunction: timingFunction, removeOnCompletion: false)
|
|
}
|
|
|
|
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
|
|
if string.range(of: "\n") != nil {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
|
|
self.textField.resignFirstResponder()
|
|
return false
|
|
}
|
|
|
|
@objc func textFieldDidChange(_ textField: UITextField) {
|
|
self.updateIsEmpty()
|
|
if let textUpdated = self.textUpdated {
|
|
textUpdated(textField.text ?? "", self.textField.textInputMode?.primaryLanguage ?? "")
|
|
}
|
|
}
|
|
|
|
private func updateIsEmpty() {
|
|
let isEmpty = !(textField.text?.isEmpty ?? true)
|
|
if isEmpty != self.textField.placeholderLabel.isHidden {
|
|
self.textField.placeholderLabel.isHidden = isEmpty
|
|
}
|
|
self.clearButton.isHidden = !isEmpty && self.prefixString == nil
|
|
}
|
|
|
|
@objc func cancelPressed() {
|
|
if let cancel = self.cancel {
|
|
cancel()
|
|
}
|
|
}
|
|
|
|
@objc func clearPressed() {
|
|
if (self.textField.text?.isEmpty ?? true) {
|
|
if self.prefixString != nil {
|
|
self.clearPrefix?()
|
|
}
|
|
} else {
|
|
self.textField.text = ""
|
|
self.textFieldDidChange(self.textField)
|
|
}
|
|
}
|
|
}
|