I made a toast view with a UIImageView in it, what I want to do is everytime the user taps the image view, the toast dismisses itself. Naturally I configured a UITapGestureRecognizer to my image view, but the selector function is not getting called. Here's what I did:
class ToastView: UIView {
private var icon: UIImageView!
init() {
super.init(frame: .zero)
setupViews()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
setupViews()
}
override func layoutSubviews() {
super.layoutSubviews()
setupConstraints()
}
private func setupViews() {
translatesAutoresizingMaskIntoConstraints = false
layer.cornerRadius = 8
isUserInteractionEnabled = true
icon = UIImageView()
icon.translatesAutoresizingMaskIntoConstraints = false
icon.image = UIImage(named: "someImage")
icon.isUserInteractionEnabled = true
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(iconHandler))
icon.addGestureRecognizer(tapGesture)
addSubview(icon)
}
private func setupConstraints() {
NSLayoutConstraint.activate([
topAnchor.constraint(equalTo: superview!.topAnchor, constant: 55),
leadingAnchor.constraint(equalTo: superview!.leadingAnchor, constant: 16),
trailingAnchor.constraint(equalTo: superview!.trailingAnchor, constant: -16),
icon.heightAnchor.constraint(equalToConstant: 16),
icon.widthAnchor.constraint(equalToConstant: 16),
icon.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -16),
icon.centerYAnchor.constraint(equalTo: centerYAnchor),
])
}
#objc private func iconHandler(_ sender: UITapGestureRecognizer) {
// This function is not called
print("handle icon")
}
}
After some research, I tried to give the ToastView a gesture recognizer instead of the image view. So I did give the tap gesture recognizer when showing the toast in my custom UIViewController class like this:
class CustomViewController: UIViewController {
private var isShowingToast: Bool = false
private lazy var toast: ToastView = {
let toast = ToastView()
toast.isUserInteractionEnabled = true
toast.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(dismissToast)))
return toast
}()
func showToastWithMessage() {
if !isShowingToast {
view.addSubview(toast)
UIView.animate(withDuration: 0.5, delay: 0.0, usingSpringWithDamping: 1.0, initialSpringVelocity: 1.0, options: .curveEaseInOut, animations: { [weak self] in
self?.toast.alpha = 1
self?.toast.frame.origin.y += 10
self?.isShowingToast = true
}, completion: { _ in
UIView.animate(withDuration: 0.5, delay: 5.0, usingSpringWithDamping: 1.0, initialSpringVelocity: 1.0, options: .curveEaseInOut, animations: { [weak self] in
self?.toast.alpha = 0
self?.toast.frame.origin.y -= 10
}, completion: { [weak self] _ in
self?.isShowingToast = false
self?.toast.removeFromSuperview()
})
})
}
}
#objc private func dismissToast() {
// This functions does not get called as well
print("dismiss")
}
}
Unfortunately the dismiss function does not print to the console. Is there anyway to resolve this?
Looks like this occurs because of your animation. View is all the time in animation status and block tap gesture. U can try call it with delay instead of adding delay for your animation.
func showToastWithMessage() {
if !isShowingToast {
view.addSubview(toast)
UIView.animate(withDuration: 0.5, delay: 0.0, usingSpringWithDamping: 1.0, initialSpringVelocity: 1.0, options: .curveEaseInOut, animations: { [weak self] in
self?.toast.alpha = 1
self?.toast.frame.origin.y += 10
self?.isShowingToast = true
}, completion: { _ in
print("Completion")
})
DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
UIView.animate(withDuration: 0.5, delay: 0.0, usingSpringWithDamping: 1.0, initialSpringVelocity: 1.0, options: .curveEaseInOut, animations: { [weak self] in
self?.toast.alpha = 0
self?.toast.frame.origin.y -= 10
}, completion: { [weak self] _ in
self?.isShowingToast = false
self?.toast.removeFromSuperview()
})
}
}
}
This way view going to animate status after 5 sec not with 5 sec delay.
This is how your controller look like:
class CustomViewController: UIViewController {
private var isShowingToast: Bool = false
lazy var toast: ToastView = {
let toast = ToastView()
toast.isUserInteractionEnabled = true
toast.backgroundColor = .red
toast.translatesAutoresizingMaskIntoConstraints = false
toast.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(dismissToast)))
return toast
}()
override func viewDidLoad() {
super.viewDidLoad()
// Add Toast constraints
view.addSubview(toast)
toast.heightAnchor.constraint(equalToConstant: 200).isActive = true
toast.widthAnchor.constraint(equalTo: toast.heightAnchor).isActive = true
toast.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
toast.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
}
func showToastWithMessage() {
if !isShowingToast {
view.addSubview(toast)
UIView.animate(withDuration: 0.5, delay: 0.0, usingSpringWithDamping: 1.0, initialSpringVelocity: 1.0, options: .curveEaseInOut, animations: { [weak self] in
self?.toast.alpha = 1
self?.toast.frame.origin.y += 10
self?.isShowingToast = true
}, completion: { _ in
UIView.animate(withDuration: 0.5, delay: 5.0, usingSpringWithDamping: 1.0, initialSpringVelocity: 1.0, options: .curveEaseInOut, animations: { [weak self] in
self?.toast.alpha = 0
self?.toast.frame.origin.y -= 10
}, completion: { [weak self] _ in
self?.isShowingToast = false
self?.toast.removeFromSuperview()
})
})
}
}
#objc private func dismissToast() {
// This functions does not get called as well
print("dismiss")
}
}
And this is your class:
class ToastView: UIView {
private var icon = UIImageView()
init() {
super.init(frame: .zero)
setupViews()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
setupViews()
}
override func layoutSubviews() {
super.layoutSubviews()
setupConstraints()
}
private func setupViews() {
translatesAutoresizingMaskIntoConstraints = false
layer.cornerRadius = 8
isUserInteractionEnabled = true
icon.translatesAutoresizingMaskIntoConstraints = false
icon.image = UIImage(named: "profilo")?.withRenderingMode(.alwaysOriginal) //put your image here
icon.isUserInteractionEnabled = true
icon.layer.cornerRadius = 8
icon.layer.masksToBounds = true //set image masked round corner
icon.clipsToBounds = true
icon.contentMode = .scaleAspectFill //set image content mode
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(iconHandler))
icon.addGestureRecognizer(tapGesture)
}
private func setupConstraints() {
// Setup the constraints for the subviews
addSubview(icon)
icon.topAnchor.constraint(equalTo: topAnchor).isActive = true
icon.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true
icon.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true
icon.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
}
#objc private func iconHandler(_ sender: UITapGestureRecognizer) {
// This function is not called
print("handle icon")
}
}
this is the result:
Related
I'm getting in trouble on UIView Animation. When I click into one of UITextFields basically everything works fine until I select another UITextField after being selected the other one, the fields move up and down then return to the same place after I select the other UITextField.
What am I doing wrong? What I expected is to move the whole UIStackView containing my fields into up to avoid the keyboard to cover it all. Also, to keep the animation static when I click into the other UITextField, just returning to the default position when the keyboard got dismissed.
class LoginViewController: UIViewController {
var coordinator: MainCoordinator?
override func viewDidLoad() {
super.viewDidLoad()
viewTapped()
setupScreen()
setupViews()
setConstraints()
}
private func setupScreen() {
self.view.backgroundColor = .systemPink
}
private func setConstraints() {
self.textFieldLogin.heightAnchor.constraint(equalToConstant: 50).isActive = true
self.textFieldLogin.widthAnchor.constraint(equalToConstant: 190).isActive = true
//
self.textFieldSenha.heightAnchor.constraint(equalToConstant: 50).isActive = true
self.textFieldSenha.widthAnchor.constraint(equalToConstant: 190).isActive = true
//
self.loginButton.widthAnchor.constraint(equalToConstant: 145).isActive = true
self.loginButton.heightAnchor.constraint(equalToConstant: 50).isActive = true
//
self.stackView.heightAnchor.constraint(equalToConstant: 200).isActive = true
self.stackView.widthAnchor.constraint(equalToConstant: 200).isActive = true
self.stackView.centerYAnchor.constraint(equalTo: self.view.centerYAnchor).isActive = true
self.stackView.centerXAnchor.constraint(equalTo: self.view.centerXAnchor).isActive = true
}
private func setupViews() {
self.view.addSubview(self.stackView)
self.viewTapped()
}
private lazy var textFieldLogin: UITextField = {
let textFieldLogin = UITextField()
textFieldLogin.tag = 1
textFieldLogin.translatesAutoresizingMaskIntoConstraints = false
textFieldLogin.layer.cornerRadius = 3.7
textFieldLogin.textAlignment = .center
textFieldLogin.placeholder = "Usuário"
textFieldLogin.backgroundColor = .white
textFieldLogin.delegate = self
return textFieldLogin
}()
private lazy var textFieldSenha: UITextField = {
let textFieldSenha = UITextField()
textFieldSenha.tag = 2
textFieldSenha.translatesAutoresizingMaskIntoConstraints = false
textFieldSenha.layer.cornerRadius = 3.7
textFieldSenha.textAlignment = .center
textFieldSenha.placeholder = "Senha"
textFieldSenha.backgroundColor = .white
textFieldSenha.delegate = self
return textFieldSenha
}()
private lazy var loginButton: UIButton = {
let loginButton = UIButton()
loginButton.translatesAutoresizingMaskIntoConstraints = false
loginButton.layer.cornerRadius = 3.8
loginButton.titleLabel?.font = UIFont(name: "Arial", size: 19)
loginButton.setTitle("Entrar", for: .normal)
loginButton.setTitleColor(.systemGreen, for: .normal)
loginButton.backgroundColor = .white
return loginButton
}()
private lazy var stackView: UIStackView = {
let stackView = UIStackView(arrangedSubviews: [self.textFieldLogin, self.textFieldSenha, self.loginButton])
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.backgroundColor = .systemBlue
stackView.axis = .vertical
stackView.distribution = .equalSpacing
return stackView
}()
private func viewTapped() {
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTap))
self.view.addGestureRecognizer(tapGesture)
}
#objc func handleTap(sender: UITapGestureRecognizer) {
UIView.animate(withDuration: 1.1, delay: 0, usingSpringWithDamping: 5.1, initialSpringVelocity: 5.0, options: .curveEaseIn, animations: {
self.stackView.frame.origin.y = self.stackView.frame.origin.y + 130
self.textFieldLogin.resignFirstResponder()
self.textFieldSenha.resignFirstResponder()
}, completion: nil)
}
}
extension LoginViewController: UITextFieldDelegate {
func textFieldDidBeginEditing(_ textField: UITextField) {
UIView.animate(withDuration: 1.1, delay: 0, usingSpringWithDamping: 5.1, initialSpringVelocity: 5.0, options: .curveEaseIn, animations: {
self.stackView.frame.origin.y = self.stackView.frame.origin.y - 130
}, completion: nil)
}
func textFieldDidEndEditing(_ textField: UITextField) {
UIView.animate(withDuration: 1.1, delay: 0, usingSpringWithDamping: 5.1, initialSpringVelocity: 5.0, options: .curveEaseIn, animations: {
self.stackView.frame.origin.y = self.stackView.frame.origin.y + 130
}, completion: nil)
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
textField.resignFirstResponder()
return true
}
}
I used the following code into textFieldDidBeginEditing and everything works fine! Thank you all.
UIView.animate(withDuration: 0.5) {
self.view.layoutIfNeeded() //Think I forget this method
self.stackView.frame.origin.y = self.stackView.frame.origin.y - 130
}
I am trying to animate UITextView by changing transform. But it makes text disappered while animating. Here is my test code.
class ViewController: UIViewController {
let textView = UITextView()
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
textView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(textView)
textView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
textView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
textView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true
textView.heightAnchor.constraint(equalToConstant: 300).isActive = true
textView.backgroundColor = .systemOrange
textView.text = "abcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcd"
textView.textColor = .white
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
let animator = UIViewPropertyAnimator(duration: 2, curve: .easeInOut, animations: {
self.textView.transform = CGAffineTransform(translationX: 0, y: 1000)
})
animator.startAnimation()
}
}
}
I tried with storyboard but the result was same. Same result when I use UIView.animate method.
Why does this happen? I want to animate it with text on.
UPDATE
let contraint = textView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor)
contraint.isActive = true
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
contraint.constant = 1000
let animator = UIViewPropertyAnimator(duration: 2, curve: .easeInOut, animations: {
self.view.layoutIfNeeded()
})
animator.startAnimation()
}
I tested with this code, and got same result.
I'm afraid you'll have to animate a snapshot view instead:
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
let v = self.textView.snapshotView(afterScreenUpdates: true)!
v.frame = self.textView.frame
self.view.addSubview(v)
self.textView.isHidden = true
let animator = UIViewPropertyAnimator(duration: 2, curve: .easeInOut, animations: {
v.frame.origin.y = 1000
})
animator.startAnimation()
}
I don't think that's a bad solution; this is what snapshot views are for, after all.
Inside my project I have Custom-DropDownView. The problem is the actual DropView is not appearing when the DropDownButton is being pressed if the DropDownButton is inside a Subview.
My structure: ViewController -> UIView -> UIStackView -> arrangedSubview ->DropdownButton
If adding the DropDownButton inside the ViewController it works just fine. However if I add it inside the StackView (where I actually need it to be) it won't show the dropView that should appear when the user taps the DropDownButton.
DropDownButton:
class DropDownBtn: UIButton, DropDownProtocol {
let label: UILabel = {
let v = UILabel()
v.font = UIFont(name: "AvenirNext-Regular", size: 15)
v.textColor = .white
v.translatesAutoresizingMaskIntoConstraints = false
return v
}()
let listImage: UIImageView = {
let v = UIImageView()
v.backgroundColor = .clear
v.translatesAutoresizingMaskIntoConstraints = false
return v
}()
func dropDownPressed(string: String, image: UIImage) {
self.setTitle("", for: .normal)
self.label.text = string
self.listImage.image = image
self.dismissDropDown()
}
var dropView = DropDownView()
var height = NSLayoutConstraint()
override init(frame: CGRect) {
super.init(frame: frame)
self.backgroundColor = UIColor.clear
self.layer.borderColor = UIColor.white.cgColor
self.layer.borderWidth = 1.0
self.layer.cornerRadius = 3
// add image
self.addSubview(listImage)
self.listImage.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 10).isActive = true
self.listImage.centerYAnchor.constraint(equalTo: self.centerYAnchor).isActive = true
self.listImage.widthAnchor.constraint(equalToConstant: 22).isActive = true
self.listImage.heightAnchor.constraint(equalToConstant: 22).isActive = true
// add label
self.addSubview(label)
self.label.leadingAnchor.constraint(equalTo: self.listImage.leadingAnchor, constant: 32).isActive = true
self.label.centerYAnchor.constraint(equalTo: self.centerYAnchor).isActive = true
dropView = DropDownView.init(frame: CGRect.init(x: 0, y: 0, width: 0, height: 0))
dropView.delegate = self
dropView.layer.borderColor = UIColor.white.cgColor
dropView.layer.borderWidth = 1.0
dropView.translatesAutoresizingMaskIntoConstraints = false
}
override func didMoveToSuperview() {
self.superview?.addSubview(dropView)
self.superview?.bringSubviewToFront(dropView)
dropView.bottomAnchor.constraint(equalTo: self.topAnchor).isActive = true
dropView.centerXAnchor.constraint(equalTo: self.centerXAnchor).isActive = true
dropView.widthAnchor.constraint(equalTo: self.widthAnchor).isActive = true
height = dropView.heightAnchor.constraint(equalToConstant: 0)
}
override func removeFromSuperview() {
dropView.bottomAnchor.constraint(equalTo: self.topAnchor).isActive = false
dropView.centerXAnchor.constraint(equalTo: self.centerXAnchor).isActive = false
dropView.widthAnchor.constraint(equalTo: self.widthAnchor).isActive = false
}
var isOpen = false
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if isOpen == false {
isOpen = true
NSLayoutConstraint.deactivate([self.height])
if self.dropView.tableView.contentSize.height > 150 {
self.height.constant = 150
} else {
self.height.constant = self.dropView.tableView.contentSize.height
}
NSLayoutConstraint.activate([self.height])
UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 0.5, initialSpringVelocity: 0.5, options: .curveEaseInOut, animations: {
self.dropView.layoutIfNeeded()
self.dropView.center.y -= self.dropView.frame.height / 2
}, completion: nil)
} else {
isOpen = false
NSLayoutConstraint.deactivate([self.height])
self.height.constant = 0
NSLayoutConstraint.activate([self.height])
UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 0.5, initialSpringVelocity: 0.5, options: .curveEaseInOut, animations: {
self.dropView.center.y += self.dropView.frame.height / 2
self.dropView.layoutIfNeeded()
}, completion: nil)
}
}
func dismissDropDown() {
isOpen = false
NSLayoutConstraint.deactivate([self.height])
self.height.constant = 0
NSLayoutConstraint.activate([self.height])
UIView.animate(withDuration: 0.2, delay: 0, options: .curveEaseIn, animations: {
self.dropView.center.y += self.dropView.frame.height / 2
self.dropView.layoutIfNeeded()
}, completion: nil)
}
Usage at the moment:
Inside UIView:
let dropDownButton: DropDownBtn = {
let v = DropDownBtn()
v.translatesAutoresizingMaskIntoConstraints = false
return v
}()
and I add it like this (+ using autolayout, the button is displayed perfectly fine and not behind any other view):
theStackView.addArrangedSubview(self.itemView)
itemView.addSubview(dropDownButton)
I tried calling self.view.bringSubViewToFront(self.view.theUIView.dropDownButton.dropView) but that didn't do anything. actual DropDownButton seems alright. I checked it in the View-Hierarchy and I also tested that touchesBegan is actually called and it is.
So I guess the issue is how i constrain the dropView but I am completely stuck here. If anyone can help me out here I am very grateful!
If anything is unclear or you need more code, just let me know!
Update:
The reason the tableView was not visible is because I forgot to call reloadData. However that does not fix the main problem !
The tableView is not clickable when adding it inside the arrangedSubview. Only if I add it to the UIView directly it becomes clickable. In both times the view-hierarchy looks the same.. It is always at the front.
If you want to have a look yourself just let me know and I give you the link to the project.
I am making a pop up menu in Swift on Xcode 8.2.1, and I can't figure out how to make it segue correctly (It is being created programmatically right now so that it will have a animation when you open it).
import UIKit
var clickj = false
var st = String()
class ViewController: UIViewController {
var button = dropDownBtn()
override func viewDidLoad() {
super.viewDidLoad()
if clickj == true {
performSegue(withIdentifier: st, sender: nil)
}
// Do any additional setup after loading the view, typically from a nib.
//Configure the button
button = dropDownBtn.init(frame: CGRect(x: 0, y: 0, width: 0, height: 0))
button.setTitle("Colors", for: .normal)
button.translatesAutoresizingMaskIntoConstraints = false
//Add Button to the View Controller
self.view.addSubview(button)
//button Constraints
button.centerXAnchor.constraint(equalTo: self.view.centerXAnchor).isActive = true
button.centerYAnchor.constraint(equalTo: self.view.centerYAnchor).isActive = true
button.widthAnchor.constraint(equalToConstant: 100).isActive = true
button.heightAnchor.constraint(equalToConstant: 40).isActive = true
//Set the drop down menu's options
button.dropView.dropDownOptions = ["Blue", "Choices"]
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
protocol dropDownProtocol {
func dropDownPressed(string : String)
}
class dropDownBtn: UIButton, dropDownProtocol {
func dropDownPressed(string: String) {
print(string)
st = string
clickj = true
self.dismissDropDown()
}
var dropView = dropDownView()
var height = NSLayoutConstraint()
override init(frame: CGRect) {
super.init(frame: frame)
self.backgroundColor = UIColor.darkGray
dropView = dropDownView.init(frame: CGRect.init(x: 0, y: 0, width: 0, height: 0))
dropView.delegate = self
dropView.translatesAutoresizingMaskIntoConstraints = false
}
override func didMoveToSuperview() {
self.superview?.addSubview(dropView)
self.superview?.bringSubview(toFront: dropView)
dropView.topAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
dropView.centerXAnchor.constraint(equalTo: self.centerXAnchor).isActive = true
dropView.widthAnchor.constraint(equalTo: self.widthAnchor).isActive = true
height = dropView.heightAnchor.constraint(equalToConstant: 0)
}
var isOpen = false
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if isOpen == false {
isOpen = true
NSLayoutConstraint.deactivate([self.height])
if self.dropView.tableView.contentSize.height > 150 {
self.height.constant = 150
} else {
self.height.constant = self.dropView.tableView.contentSize.height
}
NSLayoutConstraint.activate([self.height])
UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 0.5, initialSpringVelocity: 0.5, options: .curveEaseInOut, animations: {
self.dropView.layoutIfNeeded()
self.dropView.center.y += self.dropView.frame.height / 2
}, completion: nil)
} else {
isOpen = false
NSLayoutConstraint.deactivate([self.height])
self.height.constant = 0
NSLayoutConstraint.activate([self.height])
UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 0.5, initialSpringVelocity: 0.5, options: .curveEaseInOut, animations: {
self.dropView.center.y -= self.dropView.frame.height / 2
self.dropView.layoutIfNeeded()
}, completion: nil)
}
}
func dismissDropDown() {
isOpen = false
NSLayoutConstraint.deactivate([self.height])
self.height.constant = 0
NSLayoutConstraint.activate([self.height])
UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 0.5, initialSpringVelocity: 0.5, options: .curveEaseInOut, animations: {
self.dropView.center.y -= self.dropView.frame.height / 2
self.dropView.layoutIfNeeded()
}, completion: nil)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
class dropDownView: UIView, UITableViewDelegate, UITableViewDataSource {
var dropDownOptions = [String]()
var tableView = UITableView()
var delegate : dropDownProtocol!
override init(frame: CGRect) {
super.init(frame: frame)
tableView.backgroundColor = UIColor.darkGray
self.backgroundColor = UIColor.darkGray
tableView.delegate = self
tableView.dataSource = self
tableView.translatesAutoresizingMaskIntoConstraints = false
self.addSubview(tableView)
tableView.leftAnchor.constraint(equalTo: self.leftAnchor).isActive = true
tableView.rightAnchor.constraint(equalTo: self.rightAnchor).isActive = true
tableView.topAnchor.constraint(equalTo: self.topAnchor).isActive = true
tableView.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return dropDownOptions.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell()
cell.textLabel?.text = dropDownOptions[indexPath.row]
cell.backgroundColor = UIColor.darkGray
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
self.delegate.dropDownPressed(string: dropDownOptions[indexPath.row])
self.tableView.deselectRow(at: indexPath, animated: true)
}
}
The problem is I have every thing set up to segue properly but I am still working on where I should place the if statement that will segue when the condition is true (mainly I am looking for some function I could place it in that would be called in the main ViewController when the button is clicked).
You are close... about all you need is to implement another delegate protocol.
Currently, your dropDownView is using dropDownProtocol to tell the button that a row was selected. What you also want is a protocol so the button can tell the view controller that it has dismissed itself, and to pass the selected string from the table.
Here is the code you presented, with very few changes. See the comments in the code. With your start, things should be pretty clear - feel free to ask for clarification if needed:
import UIKit
class ViewController: UIViewController, dropDownCallBackProtocol {
var button = dropDownBtn()
override func viewDidLoad() {
super.viewDidLoad()
//Configure the button
button = dropDownBtn.init(frame: CGRect(x: 0, y: 0, width: 0, height: 0))
button.setTitle("Colors", for: .normal)
button.translatesAutoresizingMaskIntoConstraints = false
//Add Button to the View Controller
self.view.addSubview(button)
//button Constraints
button.centerXAnchor.constraint(equalTo: self.view.centerXAnchor).isActive = true
button.centerYAnchor.constraint(equalTo: self.view.centerYAnchor).isActive = true
button.widthAnchor.constraint(equalToConstant: 100).isActive = true
button.heightAnchor.constraint(equalToConstant: 40).isActive = true
//Set the drop down menu's options
button.dropView.dropDownOptions = ["Blue", "Choices"]
// set the button's "vcCallBackDelegate"
button.vcCallBackDelegate = self
}
func dropDownCompleted(string: String) {
print("Inside View Controller:", string)
// this will be called when the un-show-drop-down animation is finished
// so you can perform your segue here
// performSegue(withIdentifier: string, sender: nil)
}
}
// protocol for the button to "call back" to the view controller (its parent)
protocol dropDownCallBackProtocol {
func dropDownCompleted(string : String)
}
// protocol for the table to "call back" to the button (its parent)
protocol dropDownProtocol {
func dropDownPressed(string : String)
}
class dropDownBtn: UIButton, dropDownProtocol {
var vcCallBackDelegate: dropDownCallBackProtocol?
func dropDownPressed(string: String) {
print("Inside button class:", string)
self.dismissDropDown(string)
}
var dropView = dropDownView()
var height = NSLayoutConstraint()
override init(frame: CGRect) {
super.init(frame: frame)
self.backgroundColor = UIColor.darkGray
dropView = dropDownView.init(frame: CGRect.init(x: 0, y: 0, width: 0, height: 0))
dropView.delegate = self
dropView.translatesAutoresizingMaskIntoConstraints = false
}
override func didMoveToSuperview() {
self.superview?.addSubview(dropView)
self.superview?.bringSubview(toFront: dropView)
dropView.topAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
dropView.centerXAnchor.constraint(equalTo: self.centerXAnchor).isActive = true
dropView.widthAnchor.constraint(equalTo: self.widthAnchor).isActive = true
height = dropView.heightAnchor.constraint(equalToConstant: 0)
}
var isOpen = false
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if isOpen == false {
isOpen = true
NSLayoutConstraint.deactivate([self.height])
if self.dropView.tableView.contentSize.height > 150 {
self.height.constant = 150
} else {
self.height.constant = self.dropView.tableView.contentSize.height
}
NSLayoutConstraint.activate([self.height])
UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 0.5, initialSpringVelocity: 0.5, options: .curveEaseInOut, animations: {
self.dropView.layoutIfNeeded()
self.dropView.center.y += self.dropView.frame.height / 2
}, completion: nil)
} else {
isOpen = false
NSLayoutConstraint.deactivate([self.height])
self.height.constant = 0
NSLayoutConstraint.activate([self.height])
UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 0.5, initialSpringVelocity: 0.5, options: .curveEaseInOut, animations: {
self.dropView.center.y -= self.dropView.frame.height / 2
self.dropView.layoutIfNeeded()
}, completion: nil)
}
}
func dismissDropDown(_ selectedString: String) {
isOpen = false
NSLayoutConstraint.deactivate([self.height])
self.height.constant = 0
NSLayoutConstraint.activate([self.height])
UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 0.5, initialSpringVelocity: 0.5, options: .curveEaseInOut, animations: {
self.dropView.center.y -= self.dropView.frame.height / 2
self.dropView.layoutIfNeeded()
}, completion: {
b in
// tell the delegate the animation is complete, and pass the selected string
self.vcCallBackDelegate?.dropDownCompleted(string: selectedString)
})
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
class dropDownView: UIView, UITableViewDelegate, UITableViewDataSource {
var dropDownOptions = [String]()
var tableView = UITableView()
var delegate : dropDownProtocol!
override init(frame: CGRect) {
super.init(frame: frame)
tableView.backgroundColor = UIColor.darkGray
self.backgroundColor = UIColor.darkGray
tableView.delegate = self
tableView.dataSource = self
tableView.translatesAutoresizingMaskIntoConstraints = false
self.addSubview(tableView)
tableView.leftAnchor.constraint(equalTo: self.leftAnchor).isActive = true
tableView.rightAnchor.constraint(equalTo: self.rightAnchor).isActive = true
tableView.topAnchor.constraint(equalTo: self.topAnchor).isActive = true
tableView.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return dropDownOptions.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell()
cell.textLabel?.text = dropDownOptions[indexPath.row]
cell.backgroundColor = UIColor.darkGray
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
self.delegate.dropDownPressed(string: dropDownOptions[indexPath.row])
self.tableView.deselectRow(at: indexPath, animated: true)
}
}
I have a text view (textView) and a button (sendButton).
The button's bottom constraint is constraint to the view's bottom.
The textView becomes the first responder in viewDidAppear.
So when i present the Controller, the keyboard goes up and the button animates along with it.
Here's the code:
override func viewDidLoad() {
super.viewDidLoad()
setupSendButton()
dismissKeyboard()
}
override func viewDidAppear(_ animated: Bool) {
textView.becomeFirstResponder()
}
func setupSendButton() {
self.view.addSubview(sendButton)
sendButton.leadingAnchor.constraint(equalTo: self.view.leadingAnchor).isActive = true
sendButton.trailingAnchor.constraint(equalTo: self.view.trailingAnchor).isActive = true
sendButton.heightAnchor.constraint(equalToConstant: 60).isActive = true
sendButton.bottomAnchor.constraint(equalTo: self.view.bottomAnchor).isActive = true
sendButton.translatesAutoresizingMaskIntoConstraints = false
}
// TextView Delegate Method
func textViewDidBeginEditing(_ textView: UITextView) {
// Animation begins after textView did begin editing
animateSendButton(bottomConstraint: -216)
}
At this point everything works fine.
My problem is that when i dismiss the keyboard and end editing, I want to animate back so that the button's bottom constraint is the view's bottom constraint again.
But that doesn't work.
// TextView Delegate Method
func textViewDidEndEditing(_ textView: UITextView) {
// Animation begins after textView did end editing (it doesn't)
animateSendButton(bottomConstraint: 0)
}
// function to dismiss keyboard and end editing
func dismissKeyboard() {
let touch = UITapGestureRecognizer(target: self, action: #selector(tapGesture))
self.view.addGestureRecognizer(touch)
}
#objc func tapGesture(gesture: UITapGestureRecognizer){
// Ends editing and dismisses keyboard
self.view.endEditing(true)
}
Animate Button Funktion: animateSendButton(bottomConstraint: CGFloat)
func animateSendButton(bottomConstraint: CGFloat) {
sendButton.bottomAnchor.constraint(equalTo: self.view.bottomAnchor, constant: bottomConstraint).isActive = true
UIView.animate(withDuration: 0.55, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseIn, animations: {
self.view.layoutIfNeeded()
}, completion: nil)
}
You must delete previous bottom constraint before of adding other
or change
var bottomCon:NSLayoutConstraint!
//////
func setupSendButton() {
self.view.addSubview(sendButton)
sendButton.leadingAnchor.constraint(equalTo: self.view.leadingAnchor).isActive = true
sendButton.trailingAnchor.constraint(equalTo: self.view.trailingAnchor).isActive = true
sendButton.heightAnchor.constraint(equalToConstant: 60).isActive = true
self.bottomCon = sendButton.bottomAnchor.constraint(equalTo: self.view.bottomAnchor)
self.bottomCon.active= true
sendButton.translatesAutoresizingMaskIntoConstraints = false
}
func animateSendButton(bottomConstraint: CGFloat) {
self.bottomCon.constant = bottomConstraint
UIView.animate(withDuration: 0.55, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseIn, animations: {
self.view.layoutIfNeeded()
}, completion: nil)
}