import UIKit
class ViewController: UIViewController {
private let textField = UITextField()
private var isFirstKeyboardAppearance = true
private var accessoryView: AnimatedAccessoryView!
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemBackground
setupTextField()
setupKeyboardAccessoryView()
NotificationCenter.default.addObserver(self,
selector: #selector(keyboardWillShow),
name: UIResponder.keyboardWillShowNotification,
object: nil)
NotificationCenter.default.addObserver(self,
selector: #selector(keyboardWillHide),
name: UIResponder.keyboardWillHideNotification,
object: nil)
}
private func setupTextField() {
textField.borderStyle = .roundedRect
textField.placeholder = "ここに入力"
textField.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(textField)
NSLayoutConstraint.activate([
textField.centerXAnchor.constraint(equalTo: view.centerXAnchor),
textField.centerYAnchor.constraint(equalTo: view.centerYAnchor),
textField.widthAnchor.constraint(equalToConstant: 250),
textField.heightAnchor.constraint(equalToConstant: 40)
])
}
private func setupKeyboardAccessoryView() {
accessoryView = AnimatedAccessoryView(closeAction: { [weak self] in
self?.view.endEditing(true)
})
textField.inputAccessoryView = accessoryView
}
@objc private func keyboardWillShow() {
if isFirstKeyboardAppearance {
isFirstKeyboardAppearance = false
accessoryView.animateButtonIn()
}
}
@objc private func keyboardWillHide() {
isFirstKeyboardAppearance = true
accessoryView.resetButton()
}
}
class AnimatedAccessoryView: UIView {
private var closeButtonBottomConstraint: NSLayoutConstraint!
private var closeAction: (() -> Void)?
private let closeContainer: UIView = {
let v = UIView()
v.alpha = 0
let btn = UIButton(type: .system)
btn.setTitle("閉じる", for: .normal)
btn.translatesAutoresizingMaskIntoConstraints = false
v.addSubview(btn)
NSLayoutConstraint.activate([
btn.leadingAnchor.constraint(equalTo: v.leadingAnchor),
btn.trailingAnchor.constraint(equalTo: v.trailingAnchor),
btn.topAnchor.constraint(equalTo: v.topAnchor),
btn.bottomAnchor.constraint(equalTo: v.bottomAnchor)
])
return v
}()
private weak var innerButton: UIButton?
private let viewHeight: CGFloat = 60
init(closeAction: @escaping () -> Void) {
self.closeAction = closeAction
super.init(frame: .zero)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
private func setupLayout() {
addSubview(closeContainer)
closeContainer.translatesAutoresizingMaskIntoConstraints = false
closeButtonBottomConstraint = closeContainer.bottomAnchor
.constraint(equalTo: bottomAnchor, constant: viewHeight)
NSLayoutConstraint.activate([
closeContainer.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -16),
closeButtonBottomConstraint
])
}
private func commonInit() {
backgroundColor = .secondarySystemBackground
setupLayout()
if let button = closeContainer.subviews.first as? UIButton {
innerButton = button
button.addTarget(self,
action: #selector(didTapClose),
for: .touchUpInside)
}
let tap = UITapGestureRecognizer(target: self,
action: #selector(didTapClose))
closeContainer.addGestureRecognizer(tap)
closeContainer.isUserInteractionEnabled = true
resetButton()
}
@objc private func didTapClose() {
animateButtonOut { [weak self] in
self?.closeAction?()
}
}
func animateButtonIn() {
closeButtonBottomConstraint.constant = -8
UIView.animate(withDuration: 0.3,
delay: 0,
options: [.curveEaseInOut],
animations: {
self.layoutIfNeeded()
self.closeContainer.alpha = 1
})
}
func animateButtonOut(completion: @escaping () -> Void) {
closeButtonBottomConstraint.constant = viewHeight
UIView.animate(withDuration: 0.3,
delay: 0,
options: [.curveEaseInOut],
animations: {
self.layoutIfNeeded()
self.closeContainer.alpha = 0
}, completion: { _ in
completion()
})
}
func resetButton() {
closeButtonBottomConstraint.constant = viewHeight
closeContainer.alpha = 0
}
override var intrinsicContentSize: CGSize {
CGSize(width: UIScreen.main.bounds.width, height: viewHeight)
}
}