Button action in CollectionView cell swift - swift

I have a button in my CollectionViewCell and I added an action to the button but the action does not get triggered. I am currently using the cell independently.
class BalanceCell: UICollectionViewCell {
lazy var toggleBtn: ToggleButton = {
let view = ToggleButton()
view.isUserInteractionEnabled = true
view.addTarget(self, action: #selector(onTapped), for: .touchUpInside)
return view
}()
lazy var titleLabel: LabelX = {
let view = LabelX()
view.text = "My Available Balance"
view.font = .systemFont(ofSize: 16, weight: .regular)
return view
}()
lazy var amountLabel: TextFieldX = {
let view = TextFieldX()
view.isSecureTextEntry = true
view.text = "N000,000.00"
view.textAlignment = .center
view.font = .systemFont(ofSize: 26, weight: .medium)
return view
}()
private lazy var stackView: StackViewX = {
let view = StackViewX(arrangedSubviews: [titleLabel, amountLabel])
view.alignment = .center
view.axis = .vertical
view.distribution = .fillProportionally
view.spacing = 4
return view
}()
lazy var bgImageView: ImageViewX = {
let view = ImageViewX()
view.backgroundColor = R.color.roundEdgeButtonBgColor()
return view
}()
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func layoutSubviews() {
super.layoutSubviews()
setupViewHierarchy()
setupConstraints()
setupProperties()
translatesAutoresizingMaskIntoConstraints = false
}
func setupViewHierarchy() {
addSubview(bgImageView)
bgImageView.addSubviews([stackView, toggleBtn])
}
func setupConstraints() {
bgImageView.fillToSuperview()
stackView.centerInSuperview()
toggleBtn.anchor(verticalY: bgImageView.centerYAnchor, horizontalX: nil, paddingX: 0, paddingY: 0, width: 24, height: 24, enableInsets: false)
toggleBtn.rightAnchor.constraint(equalTo: stackView.leftAnchor, constant: -18).isActive = true
}
func setupProperties() {
bgImageView.backgroundColor = .red
bgImageView.layer.cornerRadius = 15
layer.cornerRadius = 15
}
func setup(with title: String, amount: String, bgImageViewColor: UIColor) {
titleLabel.text = title
amountLabel.text = amount
bgImageView.backgroundColor = bgImageViewColor
}
#objc private func onTapped() {
self.setSecureMode(self.toggleBtn.isSecure)
}
private func setSecureMode(_ secure: Bool) {
amountLabel.isSecureTextEntry = secure
let tempText = amountLabel.text
amountLabel.text = tempText
}
}
This is the way I use it.
lazy var balacnceView: BalanceCell = {
let view = BalanceCell()
return view
}()
ANy help as to why the Togelbutton is not getting called is appritiatedswift

The default value of isUserInteractionEnabled property is true in UIButton. You do not need to set this property to true.
However, the default value of isUserInteractionEnabled property is false in UIImageView. If you want to enable action in UIImageView (or in subviews of UIImageView), then set isUserInteractionEnabled to true.
In your code, you have put all subviews inside bgImageView. Just add this line of code:
bgImageView.isUserInteractionEnabled = true

Related

inputAccessoryView sizing problem on iPhones without physical home button

inputAccessoryView's background view is falling under its own textField and profile picture imageView.
It works fine on regular screen iPhones, but on new iPhones with notches it looks like this:
Here's how it looks animated when keyboard appears: Transition animation on becomeFirstResponder()
Here's my tableView in which I'm trying to add accessoryView:
import UIKit
import SDWebImage
class CommentsTableViewController: UITableViewController {
let viewModel = CommentsViewModel()
let postID: String
let postCaption: String
let postDate: Date
let postAuthor: ZoogramUser
var keyboardAccessoryView: CommentAccessoryView = {
let commentAccessoryView = CommentAccessoryView()
return commentAccessoryView
}()
init(post: UserPost) {
self.postID = post.postID
self.postCaption = post.caption
self.postDate = post.postedDate
self.postAuthor = post.author
super.init(style: .grouped)
self.tableView.register(PostCommentsTableViewCell.self, forCellReuseIdentifier: PostCommentsTableViewCell.identifier)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
self.title = "Comments"
keyboardAccessoryView.delegate = self
configureKeyboardAccessoryView()
viewModel.getComments(for: self.postID) {
self.tableView.reloadData()
}
tableView.backgroundColor = .systemBackground
tableView.keyboardDismissMode = .interactive
tableView.rowHeight = UITableView.automaticDimension
tableView.estimatedRowHeight = 100
tableView.allowsSelection = false
tableView.separatorStyle = .none
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
becomeFirstResponder()
}
override var inputAccessoryView: UIView? {
keyboardAccessoryView.heightAnchor.constraint(greaterThanOrEqualToConstant: 60).isActive = true
keyboardAccessoryView.backgroundColor = .systemOrange
return keyboardAccessoryView
}
override var canBecomeFirstResponder: Bool {
return true
}
func configureKeyboardAccessoryView() {
guard let photoURL = AuthenticationManager.shared.getCurrentUserProfilePhotoURL() else {
return
}
keyboardAccessoryView.userProfilePicture.sd_setImage(with: photoURL)
}
}
And here's code for my CommentAccessoryView which I use to override inputAccessoryView:
import UIKit
protocol CommentAccessoryViewProtocol {
func postButtonTapped(commentText: String)
}
class CommentAccessoryView: UIView {
var delegate: CommentAccessoryViewProtocol?
var userProfilePicture: UIImageView = {
let imageView = UIImageView()
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.contentMode = .scaleAspectFit
imageView.clipsToBounds = true
imageView.backgroundColor = .secondarySystemBackground
imageView.contentMode = .scaleAspectFill
return imageView
}()
var commentTextField: AccessoryViewTextField = {
let textField = AccessoryViewTextField()
textField.translatesAutoresizingMaskIntoConstraints = false
textField.backgroundColor = .systemBackground
textField.placeholder = "Enter comment"
textField.clipsToBounds = true
textField.layer.borderWidth = 1
textField.layer.borderColor = UIColor.placeholderText.cgColor
return textField
}()
var postButton: UIButton = {
let button = UIButton(type: .system)
button.translatesAutoresizingMaskIntoConstraints = false
button.widthAnchor.constraint(equalToConstant: 30).isActive = true
button.heightAnchor.constraint(equalToConstant: 30).isActive = true
button.clipsToBounds = true
button.layer.cornerRadius = 30/2
button.setImage(UIImage(systemName: "arrow.up.circle.fill", withConfiguration: UIImage.SymbolConfiguration(pointSize: 35)), for: .normal)
button.tintColor = .systemBlue
button.addTarget(self, action: #selector(didTapPostButton), for: .touchUpInside)
return button
}()
override init(frame: CGRect) {
super.init(frame: frame)
setupConstraints()
backgroundColor = .systemBackground
commentTextField.rightView = postButton
commentTextField.rightViewMode = .always
autoresizingMask = .flexibleHeight
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
setViewCornerRadius()
}
func setViewCornerRadius() {
userProfilePicture.layer.cornerRadius = userProfilePicture.frame.height / 2
commentTextField.layer.cornerRadius = commentTextField.frame.height / 2
}
func setupConstraints() {
self.addSubviews(userProfilePicture, commentTextField)
NSLayoutConstraint.activate([
userProfilePicture.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 10),
userProfilePicture.centerYAnchor.constraint(equalTo: self.safeAreaLayoutGuide.centerYAnchor),
userProfilePicture.widthAnchor.constraint(equalToConstant: 40),
userProfilePicture.heightAnchor.constraint(equalToConstant: 40),
commentTextField.leadingAnchor.constraint(equalTo: userProfilePicture.trailingAnchor, constant: 10),
commentTextField.centerYAnchor.constraint(equalTo: userProfilePicture.centerYAnchor),
commentTextField.heightAnchor.constraint(equalToConstant: 40),
commentTextField.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -10),
])
}
override var intrinsicContentSize: CGSize {
return CGSize.zero
}
#objc func didTapPostButton() {
guard let text = commentTextField.text else {
return
}
commentTextField.resignFirstResponder()
delegate?.postButtonTapped(commentText: text)
}
}
I've spent days trying to google a fix for that but nothing helps.
There were posts saying they were able to fix something similar by setting customView's bottom constraint to a safe area with the following method:
override func didMoveToWindow() {
if #available(iOS 11.0, *) {
if let window = window {
let bottomAnchor = bottomAnchor.constraint(lessThanOrEqualToSystemSpacingBelow: window.safeAreaLayoutGuide.bottomAnchor, multiplier: 1.0)
bottomAnchor.isActive = true
}
}
}
But when I use it, AutoLayout starts complaining.
UPDATE: I did what HangarRash recommended, changed CommentAccessoryView from UIView to UIInputView and centering profileImageView and textField to view itself and not to safe area. Now it's a little bit better, but seems to ignore safe area, inputAccessoryView should be above Home indicator but lies beneath it instead. Looking at last cell in TableView and Scroll indicator, it seems like TableView also isn't aware of inputAccessoryView and goes under it.

Design the custom table view cell programatically

I created table view cell programmatically and set the constrains as well.It is able to display the image , firstname , lastname property into cell but when I added new label with values , it not displaying the values .
Here is the cell code .
import UIKit
class PeopleCell: UITableViewCell {
static let identifier = "PeopleCell"
let containerView:UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.clipsToBounds = true // this will make sure its children do not go out of the boundary
return view
}()
let profileImageView:UIImageView = {
let img = UIImageView()
img.contentMode = .scaleAspectFill // image will never be strecthed vertially or horizontally
img.translatesAutoresizingMaskIntoConstraints = false // enable autolayout
img.layer.cornerRadius = 35
img.clipsToBounds = true
return img
}()
let firstnameTitleLabel:UILabel = {
let label = UILabel()
label.font = UIFont.boldSystemFont(ofSize: 20)
label.textColor = .black
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
let lastnameTitleLabel:UILabel = {
let label = UILabel()
label.font = UIFont.boldSystemFont(ofSize: 14)
label.textColor = .white
label.backgroundColor = #colorLiteral(red: 0.1764705926, green: 0.4980392158, blue: 0.7568627596, alpha: 1)
label.layer.cornerRadius = 5
label.clipsToBounds = true
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
let jobTitleLabel:UILabel = {
let label = UILabel()
label.font = UIFont.boldSystemFont(ofSize: 20)
label.textColor = .black
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.contentView.addSubview(profileImageView)
containerView.addSubview(firstnameTitleLabel)
containerView.addSubview(lastnameTitleLabel)
containerView.addSubview(jobTitleLabel)
self.contentView.addSubview(containerView)
profileImageView.centerYAnchor.constraint(equalTo:self.contentView.centerYAnchor).isActive = true
profileImageView.leadingAnchor.constraint(equalTo:self.contentView.leadingAnchor, constant:10).isActive = true
profileImageView.widthAnchor.constraint(equalToConstant:70).isActive = true
profileImageView.heightAnchor.constraint(equalToConstant:70).isActive = true
containerView.centerYAnchor.constraint(equalTo:self.contentView.centerYAnchor).isActive = true
containerView.leadingAnchor.constraint(equalTo:self.profileImageView.trailingAnchor, constant:10).isActive = true
containerView.trailingAnchor.constraint(equalTo:self.contentView.trailingAnchor, constant:-10).isActive = true
containerView.heightAnchor.constraint(equalToConstant:40).isActive = true
firstnameTitleLabel.topAnchor.constraint(equalTo:self.containerView.topAnchor).isActive = true
firstnameTitleLabel.leadingAnchor.constraint(equalTo:self.containerView.leadingAnchor).isActive = true
firstnameTitleLabel.trailingAnchor.constraint(equalTo:self.containerView.trailingAnchor).isActive = true
lastnameTitleLabel.topAnchor.constraint(equalTo:self.firstnameTitleLabel.bottomAnchor).isActive = true
lastnameTitleLabel.leadingAnchor.constraint(equalTo:self.containerView.leadingAnchor).isActive = true
lastnameTitleLabel.topAnchor.constraint(equalTo:self.firstnameTitleLabel.bottomAnchor).isActive = true
lastnameTitleLabel.leadingAnchor.constraint(equalTo:self.containerView.leadingAnchor).isActive = true
jobTitleLabel.topAnchor.constraint(equalTo:self.lastnameTitleLabel.bottomAnchor).isActive = true
jobTitleLabel.leadingAnchor.constraint(equalTo:self.containerView.leadingAnchor).isActive = true
jobTitleLabel.topAnchor.constraint(equalTo:self.lastnameTitleLabel.bottomAnchor).isActive = true
jobTitleLabel.leadingAnchor.constraint(equalTo:self.containerView.leadingAnchor).isActive = true
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func configureCell(firstName: String, lastName: String,jobtitle: String ) {
firstnameTitleLabel.text = "Firstname :\(firstName)"
lastnameTitleLabel.text = "Lastname : \(lastName)"
jobTitleLabel.text = "Occupation : \(jobtitle)"
}
func configureImageCell(row: Int, viewModel: ViewModel) {
profileImageView.image = nil
viewModel
.downloadImage(row: row) { [weak self] data in
let image = UIImage(data: data)
self?.profileImageView.image = image
}
}
}
Here is the view controller code .
import UIKit
import Combine
class PeopleViewController: UIViewController {
var coordinator: PeopleBaseCoordinator?
init(coordinator: PeopleBaseCoordinator) {
super.init(nibName: nil, bundle: nil)
self.coordinator = coordinator
title = "People"
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private let viewModel = ViewModel()
private var subscribers = Set<AnyCancellable>()
private var activityIndicator = UIActivityIndicatorView(style: .medium)
private lazy var tableView: UITableView = {
let tableview = UITableView()
tableview.translatesAutoresizingMaskIntoConstraints = false
tableview.dataSource = self
tableview.prefetchDataSource = self
tableview.showsVerticalScrollIndicator = false
tableview.register(PeopleCell.self, forCellReuseIdentifier: PeopleCell.identifier)
return tableview
}()
override func viewDidLoad() {
super.viewDidLoad()
activityIndicator.startAnimating()
setUpUI()
setUpBinding()
self.activityIndicator.stopAnimating()
// Do any additional setup after loading the view.
}
private func setUpUI() {
view.backgroundColor = .white
title = "People List "
view.addSubview(activityIndicator)
view.addSubview(tableView)
// self.tableView.rowHeight = 100.00
tableView.rowHeight = UITableView.automaticDimension
tableView.rowHeight = 100
tableView.topAnchor.constraint(equalTo:view.safeAreaLayoutGuide.topAnchor).isActive = true
tableView.leftAnchor.constraint(equalTo:view.safeAreaLayoutGuide.leftAnchor).isActive = true
tableView.rightAnchor.constraint(equalTo:view.safeAreaLayoutGuide.rightAnchor).isActive = true
tableView.bottomAnchor.constraint(equalTo:view.safeAreaLayoutGuide.bottomAnchor).isActive = true
// Creating constrain for Indecator
activityIndicator.translatesAutoresizingMaskIntoConstraints = false
activityIndicator.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
activityIndicator.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
}
private func setUpBinding() {
viewModel
.$peoples
.receive(on : RunLoop.main)
.sink { [weak self ] _ in
self?.tableView.reloadData()
}
.store(in: &subscribers)
viewModel.getPeople()
}
}
extension PeopleViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return viewModel.peoples.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: PeopleCell.identifier, for: indexPath) as? PeopleCell
else { return UITableViewCell() }
let row = indexPath.row
let people = viewModel.peoples[row]
cell.configureCell(firstName: people.firstName, lastName: people.lastName,jobtitle: people.jobtitle)
cell.configureImageCell(row: row, viewModel: viewModel)
return cell
}
}
extension PeopleViewController: UITableViewDataSourcePrefetching {
func tableView(_ tableView: UITableView, prefetchRowsAt indexPaths: [IndexPath]) {
viewModel.getPeople()
}
}
Here is the screenshot . As it not showing the job title property into cell .
I think your problem is the height of your containerView
containerView.heightAnchor.constraint(equalToConstant:40).isActive = true
I think 40 is to low to show the jobTitleLabel

On Change Function In Class Swift

I created a DropDownMenu class, but I need to know when the value changed in the ViewController. Like a UIScrollView didScroll, but for the value change in my class.
func scrollViewDidScroll(_ scrollView: UIScrollView)
I need something like that, but for the class!
Here is the class...
class DropDownMenu: UIStackView {
var options: [String]! = [] // Labels for all of the options
var titleButton: UIButton! = UIButton() // The Main Title Button
var target: UIViewController! // The target to get the main view. Maybe remove and automatically do it later
var textColor: UIColor! = UIColor.black // The Color of the text of the options
var bgColor: UIColor! = UIColor.clear
var borderWidth: CGFloat! = 0.0
var borderColor: CGColor! = UIColor.black.cgColor
var uiBorderColor: UIColor? = nil
var cornerRadius: CGFloat! = 0.0
var font: UIFont! = UIFont.systemFont(ofSize: 18)
var images: [UIImage]? = nil
var imageInsets: [UIEdgeInsets]? = nil
var imageInset: UIEdgeInsets? = nil
var value: String! {
get {
return currentSelected
}
set {
currentSelected = newValue
}
}
private var currentSelected: String! = ""
init(options: [String]) {
self.options = options
super.init(frame: CGRect.zero)
self.translatesAutoresizingMaskIntoConstraints = false
self.axis = .vertical
}
init(titleButton: UIButton) {
self.titleButton = titleButton
super.init(frame: CGRect.zero)
self.translatesAutoresizingMaskIntoConstraints = false
self.axis = .vertical
}
func createDropDownMenu() {
currentSelected = titleButton.titleLabel?.text
let mainFrame = titleButton.frame
print("Frame: \(mainFrame)")
print("StackView frame: \(self.frame), axis: \(self.axis)")
if uiBorderColor != nil {
borderColor = uiBorderColor!.cgColor
}
self.widthAnchor.constraint(equalToConstant: mainFrame.width).isActive = true
self.leftAnchor.constraint(equalTo: titleButton.leftAnchor).isActive = true
self.topAnchor.constraint(equalTo: titleButton.bottomAnchor, constant: self.spacing).isActive = true
var y: CGFloat = 0
var place = 0
for title in self.options {
let button = UIButton(frame: CGRect(x: 0, y: y, width: mainFrame.width, height: mainFrame.height))
button.setTitle(title, for: .normal)
button.setTitleColor(textColor, for: .normal)
button.backgroundColor = bgColor
button.addTarget(self, action: #selector(dropDownOptionClicked(_:)), for: .touchUpInside)
button.layer.cornerRadius = cornerRadius
button.layer.borderWidth = borderWidth
button.layer.borderColor = borderColor
button.titleLabel?.font = font
if images != nil {
button.setBackgroundImage(images![place], for: .normal)
if imageInsets != nil {
button.imageEdgeInsets = imageInsets![place]
} else if imageInsets == nil && imageInset != nil{
button.imageEdgeInsets = imageInset!
} else {
button.imageEdgeInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
}
}
print("Button: \(button), Title: \(String(describing: button.titleLabel?.text)), Target: \(button.allTargets)")
button.isHidden = true
button.alpha = 0
self.addArrangedSubview(button)
button.translatesAutoresizingMaskIntoConstraints = false
button.widthAnchor.constraint(equalToConstant: mainFrame.width).isActive = true
button.heightAnchor.constraint(equalToConstant: mainFrame.height).isActive = true
print("Subviews: \(self.arrangedSubviews)")
y += mainFrame.height
place += 1
print("y: \(y)")
}
}
#objc func openDropDown(_ sender: UIButton) {
print("Open DropDownMenu")
self.arrangedSubviews.forEach { (button) in
UIView.animate(withDuration: 0.7) {
button.isHidden = !button.isHidden
button.alpha = button.alpha == 0 ? 1 : 0
self.target.view.layoutIfNeeded()
}
}
}
#objc private func dropDownOptionClicked(_ sender: UIButton) {
let text = sender.titleLabel?.text
print(text as Any)
currentSelected = text
print("Value: \(String(describing: value))")
titleButton.setTitle(text, for: .normal)
openDropDown(sender)
}
init() {
super.init(frame: CGRect.zero)
self.translatesAutoresizingMaskIntoConstraints = false
self.axis = .vertical
}
required init(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Here is the creation in ViewController...
let titleButton = UIButton(frame: CGRect(x: 50, y: 290, width: 100, height: 40))
titleButton.backgroundColor = UIColor.white
titleButton.setTitle("Grade", for: .normal)
titleButton.setTitleColor(UIColor.black, for: .normal)
titleButton.layer.borderWidth = 2
titleButton.layer.cornerRadius = 10
let dp = DropDownMenu(options: ["1", "Freshman", "Sophomore", "Junior", "Senior", "College"])
dp.titleButton = titleButton
dp.target = self
dp.borderWidth = 2
dp.spacing = 5
dp.cornerRadius = 10
dp.bgColor = UIColor.white
titleButton.addTarget(dp, action: #selector(dp.openDropDown(_:)), for: .touchUpInside)
infoBox.addSubview(titleButton)
infoBox.addSubview(dp)
dp.createDropDownMenu()
The class works as expected.
I really need help on this. No answer is a bad one. This is all of my code.
You usually do that with a delegate:
class DropDownMenu: UIStackView {
weak var delegate: DropDownMenuDelegate?
var value: String! {
get {
return currentSelected
}
set {
if currentSelected != newValue {
currentSelected = newValue
self.delegate?.valueDidChange(self)
}
}
}
}
protocol DropDownMenuDelegate: class {
func valueDidChange(_ menu: DropDownMenu)
}
Then in your View Controller:
let dp = DropDownMenu(options: ["1", "Freshman", "Sophomore", "Junior", "Senior", "College"])
dp.delegate = self
(typing this in the blind as I'm away from Xcode so there may be syntax errors)

UITextView with Adjustable Font Size

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
}
}

UITapGestureRecognizer for UILabel inside of a StackView

I have a Stack View with two labels, one of which once tapped, suppose to lead to another view.
Here the code of my UIView subclass where labels and a StackView are setup:
import UIKit
import SnapKit
class WelcomeView: UIView {
weak var coordinator: MainCoordinator?
private let imageView: UIImageView = {
let imageView = UIImageView()
imageView.image = UIImage(named: "backGroundImage")
imageView.contentMode = .scaleAspectFill
imageView.layer.masksToBounds = true
return imageView
}()
private let questionLabel: UILabel = {
let questionLabel = UILabel()
questionLabel.text = "Don't have an Account?"
questionLabel.font = UIFont(name: "Avenir Next Regular", size: 17)
questionLabel.textColor = .black
return questionLabel
}()
private let signUpLabel: UILabel = {
let signUpLabel = UILabel()
let tap = UITapGestureRecognizer(target: self, action: #selector(ViewController.signUpTapped(tapGesture:)))
signUpLabel.text = "Sign Up"
signUpLabel.font = UIFont(name: "Avenir Next Regular", size: 17)
signUpLabel.textColor = .black
signUpLabel.highlightedTextColor = .link
signUpLabel.isHighlighted = true
signUpLabel.isUserInteractionEnabled = true
signUpLabel.addGestureRecognizer(tap)
return signUpLabel
}()
lazy var signUpstackView: UIStackView = {
let stackView = UIStackView(arrangedSubviews: [questionLabel, signUpLabel])
stackView.axis = .horizontal
stackView.alignment = .fill
stackView.distribution = .fill
stackView.spacing = 8
stackView.isUserInteractionEnabled = true
return stackView
}()
override init(frame: CGRect) {
super.init(frame: frame)
addSubViews()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
addSubViews()
}
func addSubViews() {
self.backgroundColor = .white
self.addSubview(imageView)
self.addSubview(btnSignIn)
self.addSubview(signUpstackView)
setConstraints()
}
func setConstraints() {
imageView.snp.makeConstraints { (make) in
make.edges.equalToSuperview()
}
btnSignIn.snp.makeConstraints { (make) in
make.height.equalTo(60)
make.bottomMargin.equalTo(-50)
make.leftMargin.equalTo(28)
make.rightMargin.equalTo(-28)
}
signUpstackView.snp.makeConstraints { (make) in
make.height.equalTo(24)
make.centerX.equalTo(self)
make.top.equalTo(btnSignIn).offset(70)
}
}
}
I added UITapGestureRecognizer in signUpLabel.
And here is the code from my ViewController containing my IBAction function signUpTapped which is specified in UITapGestureRecognizer:
class ViewController: UIViewController {
var welcomeView = WelcomeView()
override func loadView() {
view = welcomeView
}
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func signUpTapped(tapGesture:UITapGestureRecognizer) {
print("Tapped")
}
}
For some reason nothing is happened when I try to click on my SignUp Label. Is this an issue because my UILabel is inside of a StackView?
You need
Sol 1
let tap = UITapGestureRecognizer(target: self, action: #selector(self.signUpTapped(tapGesture:)))
then inside the view class
weak var delegate:ViewController?
#objc func signUpTapped(tapGesture:UITapGestureRecognizer) {
print("Tapped")
delegate?.tapped()
}
var welcomeView = WelcomeView()
override func loadView() {
welcomeView.delegate = self
view = welcomeView
}
func tapped(){}
Sol 2
weak var delegate:ViewController?
init(frame: CGRect,delegate:ViewController) {
super.init(frame: frame)
self.delegate = delegate
addSubViews()
}
With
let tap = delegate(target:delegate!, action: #selector(delegate!.signUpTapped(tapGesture:)))
and inside the vc
#objc func signUpTapped(tapGesture:UITapGestureRecognizer) {
print("Tapped")
}