Problem: Im trying to build a "comment" component to my app. I have managed to create a textview that expands as more text gets typed, and also created a button within the textview that will stay top right corner at all times. However when i write text, the text will go under the button and is not visible. What i want is that the text does not overlap the button, and instead just stays away from the button.
Question
How can i achieve this ?
This is my code:
class ClickedOnPostViewController: UIViewController, UITextFieldDelegate, UITextViewDelegate {
var answersOf: Answer?
#IBOutlet weak var tableView: UITableView!
#IBOutlet weak var commentText: UITextView!
override func viewDidLoad() {
super.viewDidLoad()
commentText.translatesAutoresizingMaskIntoConstraints = false
[
commentText.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor),
commentText.leadingAnchor.constraint(equalTo: view.leadingAnchor),
commentText.trailingAnchor.constraint(equalTo: view.trailingAnchor),
commentText.heightAnchor.constraint(equalToConstant: 43)
].forEach{ $0.isActive = true }
commentText.addSubview(button)
button.heightAnchor.constraint(equalToConstant: 50).isActive = true
button.widthAnchor.constraint(equalToConstant: 100).isActive = true
button.topAnchor.constraint(equalTo: commentText.topAnchor).isActive = true
button.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
view.bringSubview(toFront: button)
commentText.delegate = self
commentText.isScrollEnabled = false
//Keyboard listeners
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow(notification:)), name: NSNotification.Name.UIKeyboardDidShow, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide(notification:)), name: NSNotification.Name.UIKeyboardDidHide, object: nil)
// qNotificationCenter.default.addObserver(self, selector: #selector(keyboardWillChange(notification:)), name: NSNotification.Name.UIKeyboardWillChangeFrame, object: nil)
// Do any additional setup after loading the view.
}
let button: UIButton = {
let button = UIButton(type: .system)
button.backgroundColor = .orange
button.setTitle("Click Me", for: .normal)
button.translatesAutoresizingMaskIntoConstraints = false
return button
}()
#objc func keyboardWillShow(notification: NSNotification) {
if let keyboardSize = (notification.userInfo?[UIKeyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue {
if self.view.frame.origin.y == 0 {
self.view.frame.origin.y -= keyboardSize.height
}
}
}
#objc func keyboardWillHide(notification: NSNotification) {
if self.view.frame.origin.y != 0 {
self.view.frame.origin.y = 0
}
}
#IBAction func refresh(_ sender: Any) {
}
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
if text == "\n" {
commentText.resignFirstResponder()
return false
}
return true
}
func textViewDidChange(_ textView: UITextView) {
let size = CGSize(width: view.frame.width, height: .infinity)
let estimatedSize = textView.sizeThatFits(size)
textView.constraints.forEach { (constraints) in
if constraints.firstAttribute == .height {
constraints.constant = estimatedSize.height
}
}
}
}
UPDATE issues with Sh_Khan current answer:
Before typing the last character that will overlap button
After typing the last character that will overlap button
You need
commentText.translatesAutoresizingMaskIntoConstraints = false
button.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(commentText)
view.addSubview(button)
NSLayoutConstraint.activate([
commentText.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor),
commentText.leadingAnchor.constraint(equalTo: view.leadingAnchor),
commentText.heightAnchor.constraint(equalToConstant: 43),
button.heightAnchor.constraint(equalToConstant: 50),
button.widthAnchor.constraint(equalToConstant: 100),
button.topAnchor.constraint(equalTo: commentText.topAnchor),
button.trailingAnchor.constraint(equalTo: view.trailingAnchor),
button.leadingAnchor.constraint(equalTo: commentText.trailingAnchor,constant:20)
])
commentText.delegate = self
commentText.isScrollEnabled = false
Don't mix RTL with LTR logic in your case using rightAnchor with trailingAnchor
If the UI of comment textfield is build in storyboard / xib , don't set constraints for it again
to have the width of textView = 80% , remove this
button.heightAnchor.constraint(equalToConstant: 50),
and add this
commentText.widthAnchor.constraint(equalTo: view.widthAnchor,multiplier:0.8,constant:-20),
Related
I tried to make an editable uitextview with centered text that can have a maximum of 2 lines and that automatically adjusts its font size in order to fit its text within a fixed width and height.
My solution: Type some text in a UITextView, automatically copy that text and paste it in a uilabel that itself automatically adjusts its font size perfectly, and then retrieve the newly adjusted font size of the uilabel and set that size on the UITextView text.
I have spent about a month on this and repeatedly failed. I can't find a way to make it work. My attempted textview below glitches some letters out and hides large portions of out-of-bounds text instead of resizing everything. Please help me stack overflow Gods.
My attempt:
import UIKit
class TextViewViewController: UIViewController{
private let editableUITextView: UITextView = {
let tv = UITextView()
tv.font = UIFont.systemFont(ofSize: 20)
tv.text = "Delete red text, and type here"
tv.backgroundColor = .clear
tv.textAlignment = .center
tv.textContainer.maximumNumberOfLines = 2
tv.textContainer.lineBreakMode = .byWordWrapping
tv.textColor = .red
return tv
}()
private let correctTextSizeLabel: UILabel = {
let tv = UILabel()
tv.font = UIFont.systemFont(ofSize: 20)
tv.backgroundColor = .clear
tv.text = "This is properly resized"
tv.adjustsFontSizeToFitWidth = true
tv.lineBreakMode = .byTruncatingTail
tv.numberOfLines = 2
tv.textAlignment = .center
tv.textColor = .green
return tv
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor.white
view.addSubview(correctTextSizeLabel)
view.addSubview(editableUITextView)
editableUITextView.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
editableUITextView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
editableUITextView.heightAnchor.constraint(equalToConstant: 150).isActive = true
editableUITextView.widthAnchor.constraint(equalToConstant: 150).isActive = true
editableUITextView.translatesAutoresizingMaskIntoConstraints = false
editableUITextView.delegate = self
correctTextSizeLabel.leftAnchor.constraint(equalTo: editableUITextView.leftAnchor).isActive = true
correctTextSizeLabel.rightAnchor.constraint(equalTo: editableUITextView.rightAnchor).isActive = true
correctTextSizeLabel.topAnchor.constraint(equalTo: editableUITextView.topAnchor).isActive = true
correctTextSizeLabel.bottomAnchor.constraint(equalTo: editableUITextView.bottomAnchor).isActive = true
correctTextSizeLabel.translatesAutoresizingMaskIntoConstraints = false
editableUITextView.isScrollEnabled = false
}
func getApproximateAdjustedFontSizeOfLabel(label: UILabel) -> CGFloat {
if label.adjustsFontSizeToFitWidth == true {
var currentFont: UIFont = label.font
let originalFontSize = currentFont.pointSize
var currentSize: CGSize = (label.text! as NSString).size(withAttributes: [NSAttributedString.Key.font: currentFont])
while currentSize.width > label.frame.size.width * 2 && currentFont.pointSize > (originalFontSize * label.minimumScaleFactor) {
currentFont = currentFont.withSize(currentFont.pointSize - 1)
currentSize = (label.text! as NSString).size(withAttributes: [NSAttributedString.Key.font: currentFont])
}
return currentFont.pointSize
} else {
return label.font.pointSize
}
}
}
//MARK: - UITextViewDelegate
extension TextViewViewController : UITextViewDelegate {
private func textViewShouldBeginEditing(_ textView: UITextView) {
textView.becomeFirstResponder()
}
func textViewDidBeginEditing(_ textView: UITextView) {
}
func textViewDidEndEditing(_ textView: UITextView) {
}
func textViewDidChange(_ textView: UITextView) {
textView.becomeFirstResponder()
self.correctTextSizeLabel.text = textView.text
self.correctTextSizeLabel.isHidden = false
let estimatedTextSize = self.getApproximateAdjustedFontSizeOfLabel(label: self.correctTextSizeLabel)
print("estimatedTextSize: ",estimatedTextSize)
self.editableUITextView.font = UIFont.systemFont(ofSize: estimatedTextSize)
}
}
UITextField's have the option to automatically adjust font size to fit a fixed width but they only allow 1 line of text, I need it to have a maximum of 2. UILabel's solve this problem perfectly but they aren't editable.
After some searching, this looks like it will be very difficult to get working as desired.
This doesn't directly answer your question, but it may be an option:
Here's the example code:
class TestInputViewController: UIViewController {
let testLabel: InputLabel = InputLabel()
override func viewDidLoad() {
super.viewDidLoad()
let instructionLabel = UILabel()
instructionLabel.textAlignment = .center
instructionLabel.text = "Tap yellow label to edit..."
let centeringFrameView = UIView()
// label properties
let fnt: UIFont = .systemFont(ofSize: 32.0)
testLabel.isUserInteractionEnabled = true
testLabel.font = fnt
testLabel.adjustsFontSizeToFitWidth = true
testLabel.minimumScaleFactor = 0.25
testLabel.numberOfLines = 2
testLabel.setContentHuggingPriority(.required, for: .vertical)
let minLabelHeight = ceil(fnt.lineHeight)
// so we can see the frames
centeringFrameView.backgroundColor = .red
testLabel.backgroundColor = .yellow
[centeringFrameView, instructionLabel, testLabel].forEach {
$0.translatesAutoresizingMaskIntoConstraints = false
}
view.addSubview(instructionLabel)
view.addSubview(centeringFrameView)
centeringFrameView.addSubview(testLabel)
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
// instruction label centered at top
instructionLabel.topAnchor.constraint(equalTo: g.topAnchor, constant: 20.0),
instructionLabel.centerXAnchor.constraint(equalTo: g.centerXAnchor),
// centeringFrameView 20-pts from instructionLabel bottom
centeringFrameView.topAnchor.constraint(equalTo: instructionLabel.bottomAnchor, constant: 20.0),
// Leading / Trailing with 20-pts "padding"
centeringFrameView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
centeringFrameView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
// test label centered vertically in centeringFrameView
testLabel.centerYAnchor.constraint(equalTo: centeringFrameView.centerYAnchor, constant: 0.0),
// Leading / Trailing with 20-pts "padding"
testLabel.leadingAnchor.constraint(equalTo: centeringFrameView.leadingAnchor, constant: 20.0),
testLabel.trailingAnchor.constraint(equalTo: centeringFrameView.trailingAnchor, constant: -20.0),
// height will be zero if label has no text,
// so give it a min height of one line
testLabel.heightAnchor.constraint(greaterThanOrEqualToConstant: minLabelHeight),
// centeringFrameView height = 3 * minLabelHeight
centeringFrameView.heightAnchor.constraint(equalToConstant: minLabelHeight * 3.0)
])
// to handle user input
testLabel.editCallBack = { [weak self] str in
guard let self = self else { return }
self.testLabel.text = str
}
testLabel.doneCallBack = { [weak self] in
guard let self = self else { return }
// do something when user taps done / enter
}
let t = UITapGestureRecognizer(target: self, action: #selector(self.labelTapped(_:)))
testLabel.addGestureRecognizer(t)
}
#objc func labelTapped(_ g: UITapGestureRecognizer) -> Void {
testLabel.becomeFirstResponder()
testLabel.inputContainerView.theTextView.text = testLabel.text
testLabel.inputContainerView.theTextView.becomeFirstResponder()
}
}
class InputLabel: UILabel {
var editCallBack: ((String) -> ())?
var doneCallBack: (() -> ())?
override var canBecomeFirstResponder: Bool {
return true
}
override var canResignFirstResponder: Bool {
return true
}
override var inputAccessoryView: UIView? {
get { return inputContainerView }
}
lazy var inputContainerView: CustomInputAccessoryView = {
let customInputAccessoryView = CustomInputAccessoryView(frame: .zero)
customInputAccessoryView.backgroundColor = .blue
customInputAccessoryView.editCallBack = { [weak self] str in
guard let self = self else { return }
self.editCallBack?(str)
}
customInputAccessoryView.doneCallBack = { [weak self] in
guard let self = self else { return }
self.resignFirstResponder()
}
return customInputAccessoryView
}()
}
class CustomInputAccessoryView: UIView, UITextViewDelegate {
var editCallBack: ((String) -> ())?
var doneCallBack: (() -> ())?
let theTextView: UITextView = {
let tv = UITextView()
tv.isScrollEnabled = false
tv.font = .systemFont(ofSize: 16)
tv.autocorrectionType = .no
tv.returnKeyType = .done
return tv
}()
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = UIColor.white
autoresizingMask = [.flexibleHeight, .flexibleWidth]
addSubview(theTextView)
theTextView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
// constraint text view with 8-pts "padding" on all 4 sides
theTextView.topAnchor.constraint(equalTo: topAnchor, constant: 8),
theTextView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 8),
theTextView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -8),
theTextView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -8),
])
theTextView.delegate = self
}
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
if (text == "\n") {
textView.resignFirstResponder()
doneCallBack?()
}
return true
}
func textViewDidChange(_ textView: UITextView) {
editCallBack?(textView.text ?? "")
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override var intrinsicContentSize: CGSize {
return .zero
}
}
I am attempting to create a collection of UITextField elements. I'd like the next button on the keyboard to skip to the next field and if that field is hidden from view by the keyboard, scroll it into view.
This is my attempt. It works apart from 1 aspect.
When dismissing the keyboard and then selecting another (or the same) field, the text input is partially hidden by the keyboard (see attached gif).
The meat and potatoes is within the ViewController extension.
class ViewController: UIViewController {
var activeField: UITextField?
var lastOffset: CGPoint!
var keyboardHeight: CGFloat!
let scrollView: UIScrollView = {
let scrollView = UIScrollView()
scrollView.translatesAutoresizingMaskIntoConstraints = false
return scrollView
}()
let scrollViewContainer: UIStackView = {
let view = UIStackView()
view.axis = .vertical
view.spacing = 10
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
view.addSubview(scrollView)
scrollView.addSubview(scrollViewContainer)
let totalFieldCount = 25
for i in 1...totalFieldCount {
let textField = createTextField(self, placeholder: "Field #\(i)", type: .default)
textField.returnKeyType = i < totalFieldCount ? .next : .done
textField.tag = i
scrollViewContainer.addArrangedSubview(textField)
}
NSLayoutConstraint.activate([
scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
scrollView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
scrollViewContainer.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor),
scrollViewContainer.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor),
scrollViewContainer.topAnchor.constraint(equalTo: scrollView.topAnchor),
scrollViewContainer.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor),
scrollViewContainer.widthAnchor.constraint(equalTo: scrollView.widthAnchor)
])
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow), name: UIResponder.keyboardWillShowNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide), name: UIResponder.keyboardWillHideNotification, object: nil)
scrollView.keyboardDismissMode = .interactive
}
func createTextField(_ delegate: UITextFieldDelegate?, placeholder: String, type: UIKeyboardType, isSecureEntry: Bool = false) -> UITextField {
let tf = UITextField(frame: .zero)
tf.placeholder = placeholder
tf.backgroundColor = .init(white: 0, alpha: 0.03)
tf.borderStyle = .roundedRect
tf.font = .systemFont(ofSize: 14)
tf.keyboardType = type
tf.autocapitalizationType = .none
tf.autocorrectionType = .no
tf.isSecureTextEntry = isSecureEntry
tf.heightAnchor.constraint(equalToConstant: 40).isActive = true
if let delegate = delegate {
tf.delegate = delegate
}
return tf
}
}
extension ViewController: UITextFieldDelegate {
func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool {
activeField = textField
lastOffset = self.scrollView.contentOffset
return true
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
let nextTag = textField.tag + 1
if let nextResponder = textField.superview?.viewWithTag(nextTag) {
nextResponder.becomeFirstResponder()
} else {
activeField?.resignFirstResponder()
activeField = nil
}
return true
}
}
extension ViewController {
#objc func keyboardWillShow(notification: NSNotification) {
guard keyboardHeight == nil else { return }
if let keyboardSize = (notification.userInfo?[UIResponder.keyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue {
keyboardHeight = keyboardSize.height
UIView.animate(withDuration: 0.3, animations: {
self.scrollView.contentInset.bottom = self.keyboardHeight
})
guard let activeField = activeField else { return }
let distanceToBottom = self.scrollView.frame.size.height - (activeField.frame.origin.y) - (activeField.frame.size.height)
let collapseSpace = keyboardHeight - distanceToBottom
guard collapseSpace > 0 else { return }
UIView.animate(withDuration: 0.3, animations: {
self.scrollView.contentOffset = CGPoint(x: self.lastOffset.x, y: collapseSpace + 10)
})
}
}
#objc func keyboardWillHide(notification: NSNotification) {
UIView.animate(withDuration: 0.3) {
self.scrollView.contentOffset = self.lastOffset
self.scrollView.contentInset.bottom = 0
}
keyboardHeight = nil
}
}
Replace keyboardFrameBeginUserInfoKey with keyboardFrameEndUserInfoKey
Here is my code , I am adding UITextfield programtically in scrollview. But UITextField is unable to open keyboard.
It looks like UITextField is not enabling even added user interaction enabled true.
I only use the constraints, no storyboard, no xibs. Only through Constraints Programmatically.
Below is my code :
class SignupViewController : UIViewController {
var backButton : UIButton!
var titleLabel : UILabel!
var navBarView : UIView!
var scrollView : UIScrollView!
var scrollMainView : UIView!
var emailfieldView : UIView!
var emailTextField : UITextField = UITextField() override func viewDidLoad() {
super.viewDidLoad()
setDesign()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
}func setDesign(){
setNavegationBar()
setBackgroundImage()
addScrollView()
}
func setBackgroundImage(){
let backgroundImage = UIImageView(image: UIImage(named: "loginbg.png"))
self.view.addSubview(backgroundImage)
backgroundImage.translatesAutoresizingMaskIntoConstraints = false
let leadingConst = backgroundImage.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: 0)
let trailingConst = backgroundImage.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: 0)
let topConst = backgroundImage.topAnchor.constraint(equalTo: self.view.topAnchor, constant: 0)
let bottomConst = backgroundImage.bottomAnchor.constraint(equalTo: self.view.bottomAnchor, constant: 0)
NSLayoutConstraint.activate([leadingConst,trailingConst,topConst,bottomConst])
}
func setNavegationBar(){
navigationItem.title = "Join"
view.backgroundColor = UIColor.white
navigationController?.navigationBar.isHidden = true
navigationController?.navigationBar.tintColor = UIColor.white
navigationController?.navigationBar.titleTextAttributes = [NSAttributedStringKey.foregroundColor: UIColor.white]
setNavBarView()
}
func setNavBarView(){
navBarView = UIView()
self.view.addSubview(navBarView)
navBarView.translatesAutoresizingMaskIntoConstraints = false
let guide = view.safeAreaLayoutGuide
let heightCost = navBarView.heightAnchor.constraint(equalToConstant: 64.0)
let leadingCost = navBarView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: 0.0)
let trailingConst = navBarView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: 0)
let topCost = navBarView.topAnchor.constraint(equalTo: guide.topAnchor, constant: 0.0)
NSLayoutConstraint.activate([trailingConst,heightCost,topCost,leadingCost])
setBackButton()
setNavTitle()
}
func setBackButton(){
backButton = UIButton(type: UIButtonType.custom)
backButton.setImage(UIImage(named: "join_back"), for: UIControlState.normal)
navBarView.addSubview(backButton)
backButton.translatesAutoresizingMaskIntoConstraints = false
let widthCost = backButton.widthAnchor.constraint(equalToConstant: 44.0)
let heightCost = backButton.heightAnchor.constraint(equalToConstant: 44.0)
let leadingCost = backButton.leadingAnchor.constraint(equalTo: navBarView.leadingAnchor, constant: 0.0)
let topCost = backButton.topAnchor.constraint(equalTo: navBarView.topAnchor, constant: 0.0)
NSLayoutConstraint.activate([widthCost,heightCost,topCost,leadingCost])
backButton.addTarget(self, action: #selector(self.backButtonPress), for: UIControlEvents.touchUpInside)
}
func setNavTitle(){
titleLabel = UILabel()
titleLabel.text = "Join Dubai Store"
titleLabel.font = UIFont(name: "Dubai-Regular", size: 22.0)
titleLabel.textAlignment = NSTextAlignment.center
titleLabel.tintColor = UIColor(hexString: "#353535")
navBarView.addSubview(titleLabel)
titleLabel.translatesAutoresizingMaskIntoConstraints = false
let heightCost = titleLabel.heightAnchor.constraint(equalToConstant: 44.0)
let topCost = titleLabel.topAnchor.constraint(equalTo: navBarView.topAnchor, constant: 0.0)
let centerCost = titleLabel.centerXAnchor.constraint(equalTo: navBarView.centerXAnchor)
NSLayoutConstraint.activate([heightCost,topCost,centerCost])
}
#objc func backButtonPress(){
self.view.endEditing(true)
self.dismissView()
}
func addScrollView(){
scrollView = UIScrollView()
view.addSubview(scrollView)
scrollView.layer.borderWidth = 1.0
scrollView.layer.borderColor = UIColor.red.cgColor
scrollView.translatesAutoresizingMaskIntoConstraints = false
let leadingConst = scrollView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: 0)
let trailingConst = scrollView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: 0)
let topConst = scrollView.topAnchor.constraint(equalTo: navBarView.bottomAnchor, constant: 0)
let bottomConst = scrollView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor, constant: 0)
NSLayoutConstraint.activate([leadingConst,trailingConst,topConst,bottomConst])
addScrollMainView()
}
func addScrollMainView() {
scrollMainView = UIView()
scrollView.addSubview(scrollMainView)
scrollMainView.translatesAutoresizingMaskIntoConstraints = false
let leadingConst = scrollMainView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor, constant: 0)
let trailingConst = scrollMainView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: 0)
let topConst = scrollMainView.topAnchor.constraint(equalTo: scrollView.topAnchor, constant: 0)
let bottomConst = scrollMainView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor, constant: 0)
NSLayoutConstraint.activate([topConst,leadingConst,trailingConst,bottomConst])
emailFieldView()
}
func emailFieldView(){
emailfieldView = UIView()
emailfieldView.isUserInteractionEnabled = true
emailfieldView.translatesAutoresizingMaskIntoConstraints = false
scrollMainView.addSubview(emailfieldView)
let topCost = emailfieldView.topAnchor.constraint(equalTo: scrollMainView.topAnchor, constant: 0.0)
let leadingConst = emailfieldView.leadingAnchor.constraint(equalTo: scrollMainView.leadingAnchor, constant: 0)
let trailingConst = emailfieldView.trailingAnchor.constraint(equalTo: scrollMainView.trailingAnchor, constant: 0)
let heightCost = emailfieldView.heightAnchor.constraint(equalToConstant: 62.0)
NSLayoutConstraint.activate([trailingConst,heightCost,topCost,leadingConst])
//emailTextField = UITextField(frame: CGRect(x: 10, y: 0, width: SCREEN_WIDTH, height: 50))
emailTextField.placeholder = "Email"
emailTextField.layer.borderColor = UIColor.red.cgColor
emailTextField.layer.borderWidth = 1.0
// emailTextField.font = UIFont.systemFont(ofSize: 15)
// emailTextField.borderStyle = UITextBorderStyle.none
// emailTextField.keyboardType = UIKeyboardType.default
// emailTextField.returnKeyType = UIReturnKeyType.done
// //emailTextField.clearButtonMode = UITextFieldViewMode.whileEditing
emailTextField.isUserInteractionEnabled = true
emailTextField.allowsEditingTextAttributes = true
//emailTextField.contentVerticalAlignment = UIControlContentVerticalAlignment.center
emailTextField.addTarget(self, action: #selector(self.textFieldShouldBeginEditing), for: UIControlEvents.touchUpInside)
emailfieldView.addSubview(emailTextField)
emailTextField.translatesAutoresizingMaskIntoConstraints = false
emailTextField.contentMode = UIViewContentMode.left
emailTextField.delegate = self
let etopCost = emailTextField.topAnchor.constraint(equalTo: emailfieldView.topAnchor, constant: 0.0)
let eleadingConst = emailTextField.leadingAnchor.constraint(equalTo: emailfieldView.leadingAnchor, constant: 10)
let etrailingConst = emailTextField.trailingAnchor.constraint(equalTo: emailfieldView.trailingAnchor, constant: -10)
let eheightCost = emailTextField.heightAnchor.constraint(equalToConstant: 50.0)
NSLayoutConstraint.activate([etopCost,eleadingConst,etrailingConst,eheightCost])
self.scrollMainView.bringSubview(toFront: emailTextField)
emailTextField.isAccessibilityElement = true
} }
extension SignupViewController: UITextFieldDelegate {
func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool {
// return NO to disallow editing.
print("TextField should begin editing method called")
return true
}
func textFieldDidBeginEditing(_ textField: UITextField) {
// became first responder
print("TextField did begin editing method called")
}
func textFieldShouldEndEditing(_ textField: UITextField) -> Bool {
// return YES to allow editing to stop and to resign first responder status. NO to disallow the editing session to end
print("TextField should snd editing method called")
return true
}
func textFieldDidEndEditing(_ textField: UITextField) {
// may be called if forced even if shouldEndEditing returns NO (e.g. view removed from window) or endEditing:YES called
print("TextField did end editing method called")
}
func textFieldDidEndEditing(_ textField: UITextField, reason: UITextFieldDidEndEditingReason) {
// if implemented, called in place of textFieldDidEndEditing:
print("TextField did end editing with reason method called")
}
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
// return NO to not change text
print("While entering the characters this method gets called")
return true
}
func textFieldShouldClear(_ textField: UITextField) -> Bool {
// called when clear button pressed. return NO to ignore (no notifications)
print("TextField should clear method called")
return true
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
// called when 'return' key pressed. return NO to ignore.
print("TextField should return method called")
// may be useful: textField.resignFirstResponder()
return true
}
}
I just run your code, it looks like scrollMainView is not visible in the views' hierarchy. Just change the constraints. Here is the code:
func addScrollMainView() {
scrollMainView = UIView()
scrollView.addSubview(scrollMainView)
scrollMainView.translatesAutoresizingMaskIntoConstraints = false
let leadingConst = scrollMainView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: 0)
let trailingConst = scrollMainView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: 0)
let topConst = scrollMainView.topAnchor.constraint(equalTo: scrollView.topAnchor, constant: 0)
let bottomConst = scrollMainView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: 0)
NSLayoutConstraint.activate([topConst,leadingConst,trailingConst,bottomConst])
emailFieldView()
}
BTW, It is not related to your question, but it is better to create bg at the beginning. Just use the code below:
func setDesign(){
setBackgroundImage()
setNavegationBar()
addScrollView()
}
First let me say I play around with programmatic but I'm currently a novice at it.
I have mix of programmatic views and storyboard objects:
StoryBoard Objects:
Button
TextField
TextView
Programmatic Views:
messageLabel
viewForMessageLabel
When I press the button the viewForMessageLabel is added. In viewDidLoad I add a tap gesture to remove the viewForMessageLabel when the background is tapped. I also add the same tap gesture to the textField to remove the viewForMessageLabel if it's present. I again add the same tap gesture to the textField to remove it also.
If the keyboard is present I add another tap gesture to in viewDidLoad to the textField to dismiss it also. I notice things are wacky and I lose touch events.
If I press the button to add the label when I touch the background it doesn't get dismissed. If I press the textField it will dismiss it and show the keyboard. While the textField is still up if I press the button again, the label appears, I press the textField again and nothing happens. When I press return to hide the keyboard (I implemented the method), the keyboard disappears, press the button, the viewForMessageLabel appears, and I now when I press the textField the viewForMessageLabel disappears. Basically the same thing is happening with the textField.
What I want is
If the viewForMessageLabel is present and I press either the background, textField, or textView it should disappear.
If the textField's or textView's keyboard is present and I press the background the keyboard should disappear also.
My code:
class ViewController: UIViewController, UITextFieldDelegate, UITextViewDelegate {
//MARK:- Outlets
#IBOutlet weak var textField: UITextField!
#IBOutlet weak var textView: UITextView!
#IBOutlet weak var button: UIButton!
let messagelabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.text = "Pizza Pizza Pizza Pizza Pizza"
label.font = UIFont(name: "Helvetica-Regular", size: 17)
label.sizeToFit()
label.numberOfLines = 0
label.textAlignment = .center
label.textColor = UIColor.white
label.backgroundColor = UIColor.clear
return label
}()
let viewForMessageLabel: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = UIColor.red
return view
}()
//View Controller Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
textField.delegate = self
textView.delegate = self
// 0. hide viewForMessageLabel is background is tapped
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(removeViewForMessageLabel))
view.addGestureRecognizer(tapGesture)
// 1. hide viewForMessageLabel if textView is tapped
textView.addGestureRecognizer(tapGesture)
// 2. hide keyboard if background if tapped
let hideKeyboard = UITapGestureRecognizer(target: self, action: #selector(hideKeyboardWhenBackGroundTapped))
view.addGestureRecognizer(hideKeyboard)
// 3. hide keyboard if textView is tapped
textView.addGestureRecognizer(hideKeyboard)
// 4. hide viewForMessageLabel for textField if background is tapped
textField.addTarget(self, action: #selector(removeViewForMessageLabel), for: .editingDidBegin)
}
//MARK:- Button
#IBAction func buttonPressed(_ sender: UIButton) {
view.addSubview(viewForMessageLabel)
setViewForMessageLabelAnchors()
setMessageLabelAnchors()
}
//MARK:- Functions
func setViewForMessageLabelAnchors(){
viewForMessageLabel.topAnchor.constraint(equalTo: view.topAnchor, constant: 44).isActive = true
viewForMessageLabel.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 0).isActive = true
viewForMessageLabel.rightAnchor.constraint(equalTo: view.rightAnchor, constant: 0).isActive = true
viewForMessageLabel.addSubview(messagelabel)
}
func setMessageLabelAnchors(){
messagelabel.topAnchor.constraint(equalTo: viewForMessageLabel.topAnchor, constant: 0).isActive = true
messagelabel.widthAnchor.constraint(equalTo: viewForMessageLabel.widthAnchor).isActive = true
viewForMessageLabel.bottomAnchor.constraint(equalTo: messagelabel.bottomAnchor, constant: 0).isActive = true
}
func removeViewForMessageLabel(){
viewForMessageLabel.removeFromSuperview()
}
func hideKeyboardWhenBackGroundTapped(){
textField.resignFirstResponder()
}
//MARK:- TextField Delegate
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
view.endEditing(true)
return true
}
func textViewDidBeginEditing(_ textView: UITextView) {
removeViewForMessageLabel()
}
//MARK:- TextView Delegate
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
if(text == "\n") {
textView.resignFirstResponder()
return false
}
return true
}
}
If the textField's or textView's keyboard is present and I press the background the keyboard should disappear also.
You present this like it's conditional on whether the keyboard is currently shown but your code doesn't reflect that (and it shouldn't). You can call resignFirstResponder as many times as you want and nothing bad will happen. You can also call removeFromSuperview on a view that's already been removed (see here).
Thus I think you can just have one action attached to a single tap gesture recognizer:
var tapGesture: UITapGestureRecognizer?
override func viewDidLoad() {
super.viewDidLoad()
textField.delegate = self
textView.delegate = self
// 0. hide viewForMessageLabel is background is tapped
tapGesture = UITapGestureRecognizer(target: self, action: #selector(removeLabelAndHideKeyboard))
view.addGestureRecognizer(tapGesture)
// 1. hide viewForMessageLabel if textView is tapped
textView.addGestureRecognizer(tapGesture)
}
func removeLabelAndHideKeyboard() {
viewForMessageLabel.removeFromSuperview()
textField.resignFirstResponder()
}
This doesn't exactly answer the question but I found a work around. If I use the method #Toddg suggested:
func removeLabelAndHideKeyboard() {
viewForMessageLabel.removeFromSuperview()
textField.resignFirstResponder()
}
It adds resigning the textField to the function which helped tremendously.
Also inside viewDidLoad I added:
textField.addTarget(self, action: #selector(removeViewForMessageLabel), for: .touchDown)
The key there is to use .touchDown and NOT .editingDidBegin. This way I can go back and forth in between the textField and the textView and the keyboard will respond to both. I had to add 1 more thing -a toolBar to the textView's keyboard which has Done button on it to dismiss the textView:
func addDoneButtonOnKeyboard(){
let toolBar = UIToolbar()
toolBar.sizeToFit()
doneButton = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(dismissKeyboard))
toolBar.setItems([doneButton!], animated: true)
textView.inputAccessoryView = toolBar
}
#objc func dismissTextViewKeyboard(){
view.endEditing(true)
}
This way when the textView is present I can dismiss it.
In all situations if I press the textField, background, or textView and the viewForMessageLabel is present it will disappear.
If the textField is first responder and it's keyboard is present and I press the background it will disappear.
I have not figured out how to also dismiss the textView when the background is touched in addition to everything else so I implemented a Done button on the toolBar instead. If I press it and the textView's keyboard is present will get dismissed when it calls the dismissTextViewKeyboard() function I added in. Both are at the bottom and everything else is in viewDidLoad.
If anyone has a better answer I'll up vote it.
class ViewController: UIViewController, UITextFieldDelegate, UITextViewDelegate {
//MARK:- Outlets
#IBOutlet weak var textField: UITextField!
#IBOutlet weak var textView: UITextView!
#IBOutlet weak var button: UIButton!
let messagelabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.text = "Pizza Pizza Pizza Pizza Pizza"
label.font = UIFont(name: "Helvetica-Regular", size: 17)
label.sizeToFit()
label.numberOfLines = 0
label.textAlignment = .center
label.textColor = UIColor.white
label.backgroundColor = UIColor.clear
return label
}()
let viewForMessageLabel: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = UIColor.red
return view
}()
fileprivate var doneButton: UIBarButtonItem?
//View Controller Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
textField.delegate = self
textView.delegate = self
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(removeViewForMessageLabel))
view.addGestureRecognizer(tapGesture)
textField.addTarget(self, action: #selector(removeViewForMessageLabel), for: .touchDown)
addDoneButtonOnKeyboard()
}
//MARK:- Button
#IBAction func buttonPressed(_ sender: UIButton) {
//removeMessage()
view.addSubview(viewForMessageLabel)
setBackgroundAnchors()
setMessageAndLabelAnchors()
}
//MARK:- Functions
func setBackgroundAnchors(){
viewForMessageLabel.topAnchor.constraint(equalTo: view.topAnchor, constant: 44).isActive = true
viewForMessageLabel.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 0).isActive = true
viewForMessageLabel.rightAnchor.constraint(equalTo: view.rightAnchor, constant: 0).isActive = true
viewForMessageLabel.addSubview(messagelabel)
}
func setMessageAndLabelAnchors(){
messagelabel.topAnchor.constraint(equalTo: viewForMessageLabel.topAnchor, constant: 0).isActive = true
messagelabel.widthAnchor.constraint(equalTo: viewForMessageLabel.widthAnchor).isActive = true
viewForMessageLabel.bottomAnchor.constraint(equalTo: messagelabel.bottomAnchor, constant: 0).isActive = true
}
func removeViewForMessageLabel(){
viewForMessageLabel.removeFromSuperview()
textField.resignFirstResponder()
}
//MARK:- TextField Delegate
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
view.endEditing(true)
return true
}
func textViewDidBeginEditing(_ textView: UITextView) {
removeViewForMessageLabel()
}
//MARK:- TextView Delegate
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
if(text == "\n") {
textView.resignFirstResponder()
return false
}
return true
}
//MARK:- Additional Functions
//add a done button to the keyboard when the textView is first responder
fileprivate func addDoneButtonOnKeyboard(){
let toolBar = UIToolbar()
toolBar.sizeToFit()
doneButton = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(dismissTextViewKeyboard))
toolBar.setItems([doneButton!], animated: true)
textView.inputAccessoryView = toolBar
}
//dismiss the keyboard when the Done button is tapped
#objc func dismissTextViewKeyboard(){
view.endEditing(true)
}
}
I'm trying to make this view with the Layout extension, I tried a little bit, but can't figure it out.
This is my code so far:
import UIKit
import Material
class ViewController: UIViewController {
private var nameField: TextField!
private var emailField: ErrorTextField!
private var passwordField: TextField!
private let constant: CGFloat = 32
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = Color.indigo.base
prepareNameField()
preparePasswordField()
prepareResignResponderButton()
}
/// Prepares the resign responder button.
private func prepareResignResponderButton() {
let btn = FlatButton(title: "Login", titleColor: Color.white)
btn.addTarget(self, action: #selector(handleResignResponderButton(button:)), for: .touchUpInside)
view.layout(btn).width(100).height(constant).right(0).top(8 * constant).horizontally(left: constant, right: constant);
}
/// Handle the resign responder button.
#objc
internal func handleResignResponderButton(button: UIButton) {
nameField?.resignFirstResponder()
passwordField?.resignFirstResponder()
}
private func prepareNameField() {
nameField = TextField()
nameField.placeholderNormalColor = Color.indigo.lighten4
nameField.placeholderActiveColor = Color.white
nameField.dividerNormalColor = Color.indigo.lighten4
nameField.dividerActiveColor = Color.white
nameField.isClearIconButtonEnabled = true
nameField.textColor = Color.white
nameField.placeholder = "Username"
view.layout(nameField).top(4 * constant).horizontally(left: constant, right: constant)
}
private func preparePasswordField() {
passwordField = TextField()
passwordField.placeholderNormalColor = Color.indigo.lighten4
passwordField.placeholderActiveColor = Color.white
passwordField.dividerNormalColor = Color.indigo.lighten4
passwordField.dividerActiveColor = Color.white
passwordField.isClearIconButtonEnabled = true
passwordField.textColor = Color.white
passwordField.placeholder = "Password"
passwordField.clearButtonMode = .whileEditing
passwordField.isVisibilityIconButtonEnabled = true
// Setting the visibilityIconButton color.
passwordField.visibilityIconButton?.tintColor = Color.white.withAlphaComponent(passwordField.isSecureTextEntry ? 0.38 : 0.54)
view.layout(passwordField).top(6 * constant).horizontally(left: constant, right: constant)
}
}
I'm new to swift so if someone can explain me how to accomplish it that would be great.
Here is an example with TextFields.
import UIKit
import Material
class ViewController: UIViewController {
fileprivate var emailField: ErrorTextField!
fileprivate var passwordField: TextField!
/// A constant to layout the textFields.
fileprivate let constant: CGFloat = 32
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = Color.grey.lighten5
preparePasswordField()
prepareEmailField()
prepareResignResponderButton()
}
/// Prepares the resign responder button.
fileprivate func prepareResignResponderButton() {
let btn = RaisedButton(title: "Resign", titleColor: Color.blue.base)
btn.addTarget(self, action: #selector(handleResignResponderButton(button:)), for: .touchUpInside)
view.layout(btn).width(100).height(constant).centerVertically(offset: emailField.height / 2 + 60).right(20)
}
/// Handle the resign responder button.
#objc
internal func handleResignResponderButton(button: UIButton) {
emailField?.resignFirstResponder()
passwordField?.resignFirstResponder()
}
}
extension ViewController {
fileprivate func prepareEmailField() {
emailField = ErrorTextField()
emailField.placeholder = "Email"
emailField.detail = "Error, incorrect email"
emailField.isClearIconButtonEnabled = true
emailField.delegate = self
view.layout(emailField).center(offsetY: -passwordField.height - 60).left(20).right(20)
}
fileprivate func preparePasswordField() {
passwordField = TextField()
passwordField.placeholder = "Password"
passwordField.detail = "At least 8 characters"
passwordField.clearButtonMode = .whileEditing
passwordField.isVisibilityIconButtonEnabled = true
// Setting the visibilityIconButton color.
passwordField.visibilityIconButton?.tintColor = Color.green.base.withAlphaComponent(passwordField.isSecureTextEntry ? 0.38 : 0.54)
view.layout(passwordField).center().left(20).right(20)
}
}
extension UIViewController: TextFieldDelegate {
public func textFieldDidEndEditing(_ textField: UITextField) {
(textField as? ErrorTextField)?.isErrorRevealed = false
}
public func textFieldShouldClear(_ textField: UITextField) -> Bool {
(textField as? ErrorTextField)?.isErrorRevealed = false
return true
}
public func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
(textField as? ErrorTextField)?.isErrorRevealed = false
return true
}
}
As you can see below, with the visual formatting language, you're basically painting with code.
import UIKit
class ViewController: UIViewController {
var view1:UILabel!
var view2:UILabel!
var view3:UILabel!
override func viewDidLoad() {
super.viewDidLoad()
initViews();
initLayouts();
}
func initViews() {
view.backgroundColor = UIColor.white
view1 = createLabel(title: "view1")
view2 = createLabel(title: "view2")
view3 = createLabel(title: "view3")
view.addSubview(view1)
view.addSubview(view2)
view.addSubview(view3)
}
func initLayouts() {
for view in view.subviews {
view.translatesAutoresizingMaskIntoConstraints = false
}
var views = [String: AnyObject]()
views["view1"] = view1
views["view2"] = view2
views["view3"] = view3
// ---------------------------------------------------------
// Setup horizontal constraints for all three views
// ---------------------------------------------------------
view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|-20-[view1]-20-|",
options: NSLayoutFormatOptions(rawValue: 0),
metrics: nil,
views: views))
view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|-20-[view2(==view1)]-20-|",
options: NSLayoutFormatOptions(rawValue: 0),
metrics: nil,
views: views))
view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:[view3]-20-|",
options: NSLayoutFormatOptions(rawValue: 0),
metrics: nil,
views: views))
// special layout condition to make third view half the width of other views
view.addConstraint(NSLayoutConstraint(item: view3,
attribute: NSLayoutAttribute.width,
relatedBy: NSLayoutRelation.equal,
toItem: view1,
attribute: NSLayoutAttribute.width,
multiplier: 0.5,
constant: 0.0))
// ---------------------------------------------------------
// Setup vertical constraints for all three views
// ---------------------------------------------------------
view.addConstraints(
NSLayoutConstraint.constraints(withVisualFormat: "V:|-20-[view1]-20-[view2]-20-[view3]",
options: NSLayoutFormatOptions(rawValue: 0),
metrics: nil,
views: views))
// make all three views equal height
view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:[view1(44)]",
options: NSLayoutFormatOptions(rawValue: 0),
metrics: nil,
views: views))
view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:[view2(==view1)]",
options: NSLayoutFormatOptions(rawValue: 0),
metrics: nil,
views: views))
view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:[view3(==view1)]",
options: NSLayoutFormatOptions(rawValue: 0),
metrics: nil,
views: views))
}
// ------------------------------------------------
// Helper function
// ------------------------------------------------
func createLabel(title:String) -> UILabel {
let textLabel = UILabel()
textLabel.text = title
textLabel.backgroundColor = UIColor.red
textLabel.textColor = UIColor.black
textLabel.textAlignment = NSTextAlignment.center
textLabel.layer.borderColor = UIColor.black.cgColor
textLabel.layer.borderWidth = 2.0
return textLabel
}
}
You should see something like this: