How to get UITextView to anchor to the right of UICollectionViewCell? - ios

I have UITextView that I want to anchor to the right of my UICollectionViewCell. I apply the following constraint: textView.rightAnchor.constraint(equalTo: self.rightAnchor).isActive = true
But it DOES NOT anchor all the way to the right. I then apply the following constraint but this time: textView.leftAnchor.constraint(equalTo: self.leftAnchor).isActive = true and it DOES anchor all the way to the left. Why might this be?
https://imgur.com/a/uykWLw5
Here is my ChatMessageCell.
import UIKit
class ChatMessageCell: UICollectionViewCell {
let textView: UITextView = {
let tv = UITextView()
tv.text = "Some Text"
tv.font = UIFont.systemFont(ofSize: 16)
tv.translatesAutoresizingMaskIntoConstraints = false
return tv
}()
override init(frame: CGRect) {
super.init(frame: frame)
addSubview(textView)
textView.rightAnchor.constraint(equalTo: self.rightAnchor).isActive = true
textView.topAnchor.constraint(equalTo: self.topAnchor).isActive = true
textView.widthAnchor.constraint(equalToConstant: 200).isActive = true
textView.heightAnchor.constraint(equalTo: self.heightAnchor).isActive = true
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}

Your textView would be left-aligned. If you want your text view to remain with a width of 200 and appear to sit on the right you need to add: textView.textAlignment = .right
Also, I belive it's best to use leading and trailing constraints

Related

How to centre-align views in a stack view, and not stretch any of the views, or the distribution?

I am building a custom longitude/latitude selector. The view looks like:
textfield ° textfield ′ textfield ″ N|S
The components are all in a stack view. I would like the width of the textfields to auto-fit the content. Here is the code that I am using (the actual layout code is not a lot. Most of it is boilerplate):
class DMSLongLatInputView : UIView {
private var degreeTextField: DMSLongLatTextField!
private var minuteTextField: DMSLongLatTextField!
private var secondTextField: DMSLongLatTextField!
var signSelector: UISegmentedControl!
let fontSize: CGFloat = 22
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
private func commonInit() {
degreeTextField = DMSLongLatTextField()
minuteTextField = DMSLongLatTextField()
secondTextField = DMSLongLatTextField()
signSelector = UISegmentedControl(items: ["N", "S"])
signSelector.selectedSegmentIndex = 0
signSelector.setTitleTextAttributes([.font: UIFont.systemFont(ofSize: fontSize)], for: .normal)
[degreeTextField, minuteTextField, secondTextField].forEach { (tf) in
tf?.font = UIFont.monospacedDigitSystemFont(ofSize: fontSize, weight: .regular)
}
let degreeLabel = UILabel()
degreeLabel.text = "°"
let minuteLabel = UILabel()
minuteLabel.text = "′"
let secondLabel = UILabel()
secondLabel.text = "″"
[degreeLabel, minuteLabel, secondLabel].forEach {
l in l.font = UIFont.systemFont(ofSize: fontSize)
}
let stackView = UIStackView(arrangedSubviews:
[degreeTextField,
degreeLabel,
minuteTextField,
minuteLabel,
secondTextField,
secondLabel,
signSelector
])
stackView.arrangedSubviews.forEach { (v) in
// I was hoping that this would make the widths of the stack view's subviews automatically
// fit the content
v.setContentCompressionResistancePriority(.required, for: .horizontal)
v.setContentHuggingPriority(.required, for: .horizontal)
}
stackView.axis = .horizontal
stackView.alignment = .center
stackView.distribution = .fill
addSubview(stackView)
stackView.topAnchor.constraint(equalTo: self.topAnchor).isActive = true
stackView.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
stackView.leadingAnchor.constraint(equalTo: self.leadingAnchor).isActive = true
stackView.trailingAnchor.constraint(equalTo: self.trailingAnchor).isActive = true
stackView.translatesAutoresizingMaskIntoConstraints = false
backgroundColor = .clear
}
}
fileprivate class DMSLongLatTextField: UITextField, UITextFieldDelegate {
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
private func commonInit() {
backgroundColor = .tertiarySystemFill
placeholder = "00"
borderStyle = .none
textAlignment = .right
}
}
This produces:
The first textfield got stretched by a lot, even though I set the content hugging priority to .required. I would like the first textfield to be only as wide as two digits, as that is how wide its placeholder is.
I suspected that this is because I used a wrong distribution, but I tried all the other 4 distributions, but none of them got the result I wanted. For example, .equalSpacing with spacing = 0 gave this result (as seen from the UI hierarchy inspector in the debugger):
Clearly the spacing is not 0! I would like all the subviews to be as close together as they can be, and stay centre-aligned. How can I do that?
You're setting Leading and Trailing on the stack view... in other words, you're telling auto-layout to:
"Fill the width of the screen with the subviews."
Change your constraints to this:
stackView.topAnchor.constraint(equalTo: self.topAnchor).isActive = true
stackView.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
//stackView.leadingAnchor.constraint(equalTo: self.leadingAnchor).isActive = true
//stackView.trailingAnchor.constraint(equalTo: self.trailingAnchor).isActive = true
stackView.centerXAnchor.constraint(equalTo: self.centerXAnchor).isActive = true
That should horizontally center your stack view and its subviews.

addSubview doesn't work in UIView - Swift Programmatically

I need to add a UIButton as a subview on a UIView but it actually doesn't appear at runtime.
This is my code:
let moreButton : UIButton = {
let button = UIButton()
button.setImage(#imageLiteral(resourceName: "more"), for: .normal)
button.translatesAutoresizingMaskIntoConstraints = false
return button
}()
override init(frame: CGRect) {
super.init(frame: frame)
addSubview(moreButton)
moreButton.trailingAnchor.constraint(equalTo: self.trailingAnchor).isActive = true
moreButton.topAnchor.constraint(equalTo: self.topAnchor).isActive = true
moreButton.heightAnchor.constraint(equalToConstant: 30).isActive = true
moreButton.widthAnchor.constraint(equalToConstant: 20).isActive = true
}
The button isn't added eventually to the view. I'm sure this is an easy fix but I can't wrap my head around it.
First of all, make sure you the viewController is showing anything since you're doing it without storyboards, check out this simple tutorial:
https://medium.com/better-programming/creating-a-project-without-storyboard-in-2020-and-without-swifui-82080eb6d13b
If the problem is with that UIButton, try to set up the subviews in viewDidLoad:
final class ViewController: UIViewController {
let cardView = CardView()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(cardView)
/// Constraints
let margins = view.layoutMarginsGuide
cardView.trailingAnchor.constraint(equalTo: margins.trailingAnchor).isActive = true
cardView.leadingAnchor.constraint(equalTo: margins.leadingAnchor).isActive = true
cardView.topAnchor.constraint(equalTo: margins.topAnchor).isActive = true
cardView.heightAnchor.constraint(equalToConstant: 30).isActive = true
cardView.widthAnchor.constraint(equalToConstant: 20).isActive = true
}
}
final class CardView: UIView {
let moreButton : UIButton = {
let button = UIButton()
button.setTitle("Button title", for: .normal)
button.translatesAutoresizingMaskIntoConstraints = false
return button
}()
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
}
override init(frame: CGRect) {
super.init(frame:frame)
self.translatesAutoresizingMaskIntoConstraints = false
self.addSubview(moreButton)
/// Constraints
let margins = self.layoutMarginsGuide
moreButton.trailingAnchor.constraint(equalTo: margins.trailingAnchor).isActive = true
moreButton.leadingAnchor.constraint(equalTo: margins.leadingAnchor).isActive = true
moreButton.topAnchor.constraint(equalTo: margins.topAnchor).isActive = true
moreButton.heightAnchor.constraint(equalToConstant: 30).isActive = true
moreButton.widthAnchor.constraint(equalToConstant: 20).isActive = true
}
}
I've set up margins for the constraints and also added a leadingAnchor constraint, that might have been the issue as well.

How can I add some insets to the text inside the UILabel?

I'm trying to add insets to the text inside the UILabel without subclassing it. Or even with UILabel subclass but without changing too much the code.
How can I do it?
class CustomCell: UICollectionViewCell {
var data:CustomData? {
didSet {
guard let data = data else { return }
//bg.image = data.image
bg.text = data.title
}
}
fileprivate let bg: UILabel = {
let iv = UILabel()
iv.layer.backgroundColor = UIColor.gray.cgColor
iv.textAlignment = .center
iv.numberOfLines = 0
iv.font = UIFont(name: "Helvetica", size: 40)
iv.adjustsFontSizeToFitWidth = true
iv.minimumScaleFactor = 0.5
iv.translatesAutoresizingMaskIntoConstraints = false
iv.layer.cornerRadius = 12
return iv
}()
override init(frame: CGRect) {
super.init(frame: frame)
contentView.addSubview(bg)
bg.topAnchor.constraint(equalTo: contentView.topAnchor).isActive = true
bg.leadingAnchor.constraint(equalTo: contentView.leadingAnchor).isActive = true
bg.trailingAnchor.constraint(equalTo: contentView.trailingAnchor).isActive = true
bg.bottomAnchor.constraint(equalTo: contentView.bottomAnchor).isActive = true
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Use constant parameter
bg.topAnchor.constraint(equalTo: contentView.topAnchor,constant:30).isActive = true
for 30 pts inset do
NSLayoutConstraint.activate([
bg.topAnchor.constraint(equalTo: contentView.topAnchor,constant:30),
bg.leadingAnchor.constraint(equalTo: contentView.leadingAnchor,constant:30),
bg.trailingAnchor.constraint(equalTo: contentView.trailingAnchor,constant:-30),
bg.bottomAnchor.constraint(equalTo: contentView.bottomAnchor,constant:-30)
])
You can also set UIEdgeInsets
Your entire approach is wrong. If the goal is to show the label text centered in bg, then bg should not be a label; it should contain a label, centered.
This example uses no code at all; the outer view self-sizes to the label, and the label's constraints to the outer view provide the insets:

How do I align ImageView to the bottom of ScrollView programmatically?

I am trying to align a background image to the bottom of a scroll view that fits the screen, programmatically using Autolayout. Ideally, I want the image to be always aligned at the bottom of the scroll view. When the content of the scroll view goes beyond the screen height or when scroll view content size is less than screen height with scroll view fitting the whole screen.
MyView
class MyView: UIView {
let myScrollView: UIScrollView = {
let scrollView = UIScrollView()
scrollView.translatesAutoresizingMaskIntoConstraints = false
scrollView.bounces = false
return scrollView
}()
let contentView: UIView = {
let view = UIView()
view.backgroundColor = .red
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
let myLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.text = "Hello world"
label.font = UIFont.systemFont(ofSize: 24)
return label
}()
let myImageView: UIImageView = {
let imageView = UIImageView()
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.image = #imageLiteral(resourceName: "Mask Group 3")
return imageView
}()
override init(frame: CGRect) {
super.init(frame: frame)
setupView()
setupConstraints()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func setupView() {
backgroundColor = .white
addSubview(myScrollView)
myScrollView.addSubview(contentView)
contentView.addSubview(myLabel)
contentView.addSubview(myImageView)
}
private func setupConstraints() {
myScrollView.topAnchor.constraint(equalTo: topAnchor).isActive = true
myScrollView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
myScrollView.leftAnchor.constraint(equalTo: leftAnchor).isActive = true
myScrollView.rightAnchor.constraint(equalTo: rightAnchor).isActive = true
contentView.topAnchor.constraint(equalTo: myScrollView.topAnchor).isActive = true
contentView.bottomAnchor.constraint(equalTo: myScrollView.bottomAnchor).isActive = true
contentView.leftAnchor.constraint(equalTo: myScrollView.leftAnchor).isActive = true
contentView.rightAnchor.constraint(equalTo: myScrollView.rightAnchor).isActive = true
contentView.widthAnchor.constraint(equalTo: widthAnchor).isActive = true
// If I am setting this and when the content size go beyond the screen, it does not scroll
// If I don't set this, there is no content size and image view will not position correctly
// contentView.heightAnchor.constraint(equalTo: heightAnchor).isActive = true
myLabel.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 200).isActive = true
myLabel.centerXAnchor.constraint(equalTo: contentView.centerXAnchor).isActive = true
myImageView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor).isActive = true
myImageView.leftAnchor.constraint(equalTo: contentView.leftAnchor).isActive = true
myImageView.rightAnchor.constraint(equalTo: contentView.rightAnchor).isActive = true
}
}
MyViewController
import UIKit
class MyViewController: UIViewController {
override func loadView() {
view = MyView()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
navigationController?.setNavigationBarHidden(true, animated: false)
}
override func viewDidLoad() {
super.viewDidLoad()
}
}
Illustration
I have found the solution.
A contentView is needed.
Set the contentView's top, left, bottom, right constraint equal to scrollView edges.
Set the contentView's width equal to view's width anchor
Set the contentView's height greaterThanOrEqualTo view's height anchor
Set the imageView's bottom equal to the bottom anchor of contentView.
For the imageView's top, set the constraint to an element with greaterThanOrEqualTo, to give it a constant gap and avoid overlapping of elements in smaller screens.
It is seems ok:
private func setupConstraints() {
myScrollView.topAnchor.constraint(equalTo: topAnchor).isActive = true
myScrollView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
myScrollView.leftAnchor.constraint(equalTo: leftAnchor).isActive = true
myScrollView.rightAnchor.constraint(equalTo: rightAnchor).isActive = true
contentView.topAnchor.constraint(equalTo: myScrollView.topAnchor).isActive = true
contentView.bottomAnchor.constraint(equalTo: myScrollView.bottomAnchor).isActive = true
contentView.leftAnchor.constraint(equalTo: myScrollView.leftAnchor).isActive = true
contentView.rightAnchor.constraint(equalTo: myScrollView.rightAnchor).isActive = true
contentView.widthAnchor.constraint(equalTo: widthAnchor).isActive = true
// If I am setting this and when the content size go beyond the screen, it does not scroll
// If I don't set this, there is no content size and image view will not position correctly
contentView.heightAnchor.constraint(equalToConstant: 1400).isActive = true
myLabel.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 200).isActive = true
myLabel.centerXAnchor.constraint(equalTo: contentView.centerXAnchor).isActive = true
myImageView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor).isActive = true
myImageView.leftAnchor.constraint(equalTo: contentView.leftAnchor).isActive = true
myImageView.rightAnchor.constraint(equalTo: contentView.rightAnchor).isActive = true
myImageView.heightAnchor.constraint(equalToConstant: 200).isActive = true
}
If think you just forgot to specify image height:
myImageView.heightAnchor.constraint(equalToConstant: 200).isActive = true

TextView not resizing properly

I have a tableview cell with a textview inside it:
class DetailsCell: UITableViewCell, UITextViewDelegate {
let detailsTextView: UITextView = {
let tv = UITextView()
tv.font = UIFont(name: "AvenirNext-Medium", size: 24)
tv.isScrollEnabled = false
tv.textColor = .white
tv.backgroundColor = .clear
tv.translatesAutoresizingMaskIntoConstraints = false
return tv
}()
func textViewDidChange(_ textView: UITextView) {
let addTodoViewController = AddTodoViewController()
addTodoViewController.begindEndUpdate()
}
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: .default, reuseIdentifier: "DetailsCell")
addSubview(detailsTextView)
detailsTextView.topAnchor.constraint(equalTo: topAnchor, constant: 10).isActive = true
detailsTextView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
detailsTextView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -10).isActive = true
detailsTextView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 10).isActive = true
backgroundColor = .black
textLabel?.isHidden = true
detailsTextView.delegate = self
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
When I load the app and the textview already has a text, assigned programmatically, the cell properly get's the right height and the textview is properly resized, but when I try to change the text of the textview, the cell and the textview don't resize. Can someone help me?
override func viewWillAppear(_ animated: Bool) {
toDoTextField.becomeFirstResponder()
addTodoTableView.estimatedRowHeight = 60
addTodoTableView.rowHeight = UITableViewAutomaticDimension
}
Textview with not initial text Image
Textview with initial text Image
UITextView does not resize itself when its content changes. You have to write some code to resize your textView and then accordingly adjust the height of your cell. Probably this can help you resizing textview to its content
or you can use some third party library instead of default UITextView like this one GrowingTextView

Resources