I have a custom UITextView class that initializes a new TextView. The delegate of this class is itself as I need code to run when the text is changed, the delegate method runs. Here is that class.
protocol TextViewOuterDelegate: class {
func textViewDidChange(_ textView: UITextView)
}
/// Custom `UITextView`
class TextView: UITextView, UITextViewDelegate {
var hintLabel, smallHintLabel: UILabel!
var underline: UIView!
var secondDelegate: TextViewOuterDelegate?
// MARK: - Init
/// Initialize text field
/// - Parameters:
/// - text: Text field text
/// - hintText: Text field hint text (a.k.a. placeholder/description)
/// - isLight: Whether text field is light style
convenience init(text: String? = nil, hintText: String, isLight: Bool = false) {
self.init(frame: .zero, textContainer: nil)
self.heightAnchor.constraint(equalToConstant: 57).isActive = true
self.textContainerInset = UIEdgeInsets(top: 25, left: 0, bottom: 0, right: 0)
self.delegate = self
let weight: UIFont.Weight = isLight ? .medium : .regular
let textColor: UIColor = isLight ? .white : .black
let hintColor = isLight ? UIColor(white: 1.0, alpha: 0.8) : UIColor(white: 0.0, alpha: 0.4)
let lineColor = isLight ? UIColor(white: 1.0, alpha: 0.3) : UIColor(white: 0.0, alpha: 0.12)
let largeSize = UIFont.preferredFont(forTextStyle: .title3).pointSize
let smallSize = UIFont.preferredFont(forTextStyle: .subheadline).pointSize
// hint
hintLabel = UILabel()
hintLabel.text = hintText
hintLabel.font = UIFont.systemFont(ofSize: largeSize, weight: weight)
hintLabel.textColor = hintColor
hintLabel.alpha = text == nil || text!.isBlank ? 1 : 0
self.addSubview(hintLabel)
hintLabel.translatesAutoresizingMaskIntoConstraints = false
hintLabel.centerYAnchor.constraint(equalTo: self.centerYAnchor, constant: insets.top / 2).isActive = true
hintLabel.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: insets.left
).isActive = true
hintLabel.trailingAnchor.constraint(equalTo: self.trailingAnchor).isActive = true
// small hint
smallHintLabel = UILabel()
self.smallHintLabel.text = hintText
self.smallHintLabel.font = UIFont.systemFont(ofSize: smallSize, weight: weight)
self.smallHintLabel.textColor = hintColor
self.smallHintLabel.alpha = text == nil || text!.isBlank ? 0 : 1
self.addSubview(smallHintLabel)
smallHintLabel.translatesAutoresizingMaskIntoConstraints = false
smallHintLabel.topAnchor.constraint(equalTo: self.topAnchor).isActive = true
smallHintLabel.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: insets.left).isActive = true
smallHintLabel.trailingAnchor.constraint(equalTo: self.trailingAnchor).isActive = true
// text
self.text = text
self.font = UIFont.systemFont(ofSize: largeSize, weight: weight)
self.textColor = textColor
self.tintColor = isLight ? .white : Color.blue
self.keyboardAppearance = isLight ? .dark : .light
self.isScrollEnabled = false
// underline
underline = UIView()
underline.backgroundColor = lineColor
self.addSubview(underline)
underline.translatesAutoresizingMaskIntoConstraints = false
underline.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
underline.leadingAnchor.constraint(equalTo: self.leadingAnchor).isActive = true
underline.trailingAnchor.constraint(equalTo: self.trailingAnchor).isActive = true
underline.heightAnchor.constraint(equalToConstant: 1).isActive = true
}
override init(frame: CGRect, textContainer: NSTextContainer?) {
super.init(frame: frame, textContainer: textContainer)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
func textViewDidChange(_ textView: UITextView) {
/// Adjusts the position of the hint text when text is changed in field
if self.text?.isEmpty ?? true {
self.moveHintDown()
} else {
self.moveHintUp()
}
secondDelegate?.textViewDidChange(self)
/// Adjusts the size of the text view when text is changed in field
let size = CGSize(width: textView.visibleSize.width, height: 999999)
let estimatedSize = self.sizeThatFits(size)
self.constraints.forEach { constraint in
if constraint.firstAttribute == .height {
//TODO: Max num of lines (prob no restriction), scroll the messages every time that a new line is added, fix this code to make it nicer
if estimatedSize.height < 57 {
constraint.constant = 57
} else {
constraint.constant = estimatedSize.height
}
}
}
}
In order to run a function every time that the text is changed on a different file, I set up a TextViewOuterDelegate which should communicate with my other file in order to run the other function.
class MessageComposeView: UIView, TextViewOuterDelegate {
private var textField: TextView!
private var sendButton: SendButton!
weak var delegate: MessageComposeViewDelegate?
init() {
super.init(frame: .zero)
// text field
textField = TextView(hintText: "Type a message")
let test = TextView()
test.secondDelegate = self
self.addSubview(textField)
textField.translatesAutoresizingMaskIntoConstraints = false
textField.topAnchor.constraint(equalTo: self.topAnchor, constant: 30).isActive = true
textField.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: -30).isActive = true
textField.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 16).isActive = true
// send button
sendButton = SendButton()
sendButton.setState(.disabled)
sendButton.addTarget(self, action: #selector(send), for: .touchUpInside)
self.addSubview(sendButton)
sendButton.centerYAnchor.constraint(equalTo: self.centerYAnchor).isActive = true
sendButton.leadingAnchor.constraint(equalTo: textField.trailingAnchor, constant: 16).isActive = true
sendButton.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -16).isActive = true
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
// MARK: - Actions
/// Called when the message text changes
func textViewDidChange(_ textView: UITextView) {
sendButton.setState(textField.text?.isBlank == true ? .disabled : .normal)
}
/// Calls `sendMessage(text: String?)` delegate method to send the message and disable the send button
/// Called when message is sent to reset the compose view
func sent() {
textField.text = nil
textViewDidChange(textField)
sendButton.stopLoad()
sendButton.setState(.disabled)
}
}
The secondDelegate is not being set and therefore the TextViewDidChange func is not running. What am I doing wrong? Thank you in advance.
// This is the instance that you should assign secondDelegate for
textField = TextView(hintText: "Type a message")
// Not this one, this one never gets added as a subview
let test = TextView()
test.secondDelegate = self
// The fix is here
textField.secondDelegate = self
self.addSubview(textField)
Related
Exploring stackviews I've ran into a problem of incorrect representation if views inside of it. So, to make a long story short...
I've made a custom checkbox:
class CheckBox: UIView, CheckBoxProtocol {
required init(frame: CGRect, color: UIColor) {
super.init(frame: frame)
self.layer.borderWidth = 5
self.layer.borderColor = color.cgColor
self.addSubview(checkmark)
checkmark.tintColor = color
let gesture = UITapGestureRecognizer(target: self, action: #selector(toggle))
self.addGestureRecognizer(gesture)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
var isChecked = true
lazy var checkmark: UIImageView = {
let imageView = UIImageView(frame: CGRect(x: 0, y: 0, width: bounds.width, height: bounds.height))
imageView.isHidden = false
imageView.contentMode = .scaleAspectFit
imageView.image = UIImage(systemName: "checkmark")
return imageView
}()
#objc func toggle() {
self.isChecked.toggle()
self.checkmark.isHidden = !self.isChecked
}
In the Controller, when I add this view to the subviews it looks fairly normal and works as it should work (check-uncheck)
However when I add checkbox to the stackview it looses its visible frame and its functionality (does not check-uncheck) - you can see it on the screenshot
screenshot
Here is the code from the ViewController:
class SettingsViewController: UIViewController {
override func loadView() {
super.loadView()
self.view.backgroundColor = .white
self.view.addSubview(stackView)
}
override func viewDidLoad() {
super.viewDidLoad()
}
lazy var stackView: UIStackView = {
let stackView = UIStackView(frame: CGRect(x: 150, y: 150, width: 0, height: 0))
stackView.axis = .horizontal
stackView.spacing = 50
stackView.alignment = .fill
stackView.distribution = .fillEqually
[redCheckbox,
greenCheckbox,
blackCheckbox,
greyCheckbox,
brownCheckbox,
yellowCheckbox,
purpleCheckbox,
orangeCheckbox].forEach {stackView.addArrangedSubview($0)}
return stackView
}()
private let frame = CGRect(x: 0, y: 0, width: 30, height: 30)
lazy var redCheckbox: CheckBox = {
let colorFactory = CardViewFactory()
let color = colorFactory.getViewColor(modelColor: CardColor.red)
let checkbox = CheckBox(frame: frame, color: color)
return checkbox
}()
lazy var greenCheckbox: CheckBox = {
let colorFactory = CardViewFactory()
let color = colorFactory.getViewColor(modelColor: CardColor.green)
let checkbox = CheckBox(frame: frame, color: color)
return checkbox
}()
lazy var blackCheckbox: CheckBox = {
let colorFactory = CardViewFactory()
let color = colorFactory.getViewColor(modelColor: CardColor.black)
let checkbox = CheckBox(frame: frame, color: color)
return checkbox
}()
lazy var greyCheckbox: CheckBox = {
let colorFactory = CardViewFactory()
let color = colorFactory.getViewColor(modelColor: CardColor.grey)
let checkbox = CheckBox(frame: frame, color: color)
return checkbox
}()
lazy var brownCheckbox: CheckBox = {
let colorFactory = CardViewFactory()
let color = colorFactory.getViewColor(modelColor: CardColor.brown)
let checkbox = CheckBox(frame: frame, color: color)
return checkbox
}()
lazy var yellowCheckbox: CheckBox = {
let colorFactory = CardViewFactory()
let color = colorFactory.getViewColor(modelColor: CardColor.yellow)
let checkbox = CheckBox(frame: frame, color: color)
return checkbox
}()
lazy var purpleCheckbox: CheckBox = {
let colorFactory = CardViewFactory()
let color = colorFactory.getViewColor(modelColor: CardColor.purple)
let checkbox = CheckBox(frame: frame, color: color)
return checkbox
}()
lazy var orangeCheckbox: CheckBox = {
let colorFactory = CardViewFactory()
let color = colorFactory.getViewColor(modelColor: CardColor.orange)
let checkbox = CheckBox(frame: frame, color: color)
return checkbox
}()
It's because we're working with the lazy property and its life cycle can be a little different. Let's set constraints after the view has loaded. What I would suggest to do:
For each checkbox, change the frame to zero:
lazy var orangeCheckbox: CheckBox = {
let colorFactory = CardViewFactory()
let color = colorFactory.getViewColor(modelColor: CardColor.orange)
let checkbox = CheckBox(frame: .zero, color: colorFactory)
checkbox.translatesAutoresizingMaskIntoConstraints = false
return checkbox
}()
Do the same to the stackView:
lazy var stackView: UIStackView = {
let stackView = UIStackView(frame: .zero)
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.axis = .horizontal
stackView.spacing = 5
stackView.alignment = .fill
stackView.distribution = .fillEqually
[redCheckbox,
greenCheckbox,
blackCheckbox,
greyCheckbox,
brownCheckbox,
yellowCheckbox,
purpleCheckbox,
orangeCheckbox].forEach {stackView.addArrangedSubview($0)}
return stackView
}()
Add some constraints on viewDidLoad:
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
view.addSubview(stackView)
stackView.centerYAnchor.constraint(equalTo: view.safeAreaLayoutGuide.centerYAnchor, constant: -100).isActive = true
stackView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 10).isActive = true
stackView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: 10).isActive = true
stackView.heightAnchor.constraint(equalToConstant: 40).isActive = true
[redCheckbox,
greenCheckbox,
blackCheckbox,
greyCheckbox,
brownCheckbox,
yellowCheckbox,
purpleCheckbox,
orangeCheckbox].forEach {
$0.heightAnchor.constraint(equalToConstant: 30).isActive = true
$0.widthAnchor.constraint(equalToConstant: 30).isActive = true
}
}
What you can do to the image inside the checkBox to work fine:
translatesAutoresizingMaskIntoConstraints = false
lazy var checkmark: UIImageView = {
let imageView = UIImageView(frame: CGRect(x: 0, y: 0, width: 20, height: 20))
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.isHidden = false
imageView.contentMode = .scaleAspectFit
imageView.image = UIImage(systemName: "checkmark")
return imageView
}()
on your required init:
required init(frame: CGRect, color: UIColor) {
super.init(frame: frame)
self.layer.borderWidth = 5
self.layer.borderColor = color.cgColor
self.addSubview(checkmark)
checkmark.tintColor = color
checkmark.topAnchor.constraint(equalTo: topAnchor).isActive = true
checkmark.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true
checkmark.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true
checkmark.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
let gesture = UITapGestureRecognizer(target: self, action: #selector(toggle))
self.addGestureRecognizer(gesture)
setNeedsDisplay()
}
To offer some additional info...
You can save yourself a lot of duplicate coding.
Take a look at this...
First, slight modifications to your Checkbox class:
class CheckBox: UIView {//, CheckBoxProtocol {
var checkmark: UIImageView = {
let imageView = UIImageView()
imageView.contentMode = .scaleAspectFit
imageView.image = UIImage(systemName: "checkmark")
return imageView
}()
var isChecked = true {
didSet {
checkmark.isHidden = !isChecked
}
}
required init(frame: CGRect, color: UIColor) {
super.init(frame: frame)
commonInit(color)
}
override init(frame: CGRect) {
super.init(frame: frame)
commonInit(.white)
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit(.white)
}
func commonInit(_ color: UIColor) {
self.layer.borderWidth = 5
self.layer.borderColor = color.cgColor
self.addSubview(checkmark)
checkmark.tintColor = color
checkmark.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
// constrain image view to all 4 sides
checkmark.topAnchor.constraint(equalTo: topAnchor),
checkmark.leadingAnchor.constraint(equalTo: leadingAnchor),
checkmark.trailingAnchor.constraint(equalTo: trailingAnchor),
checkmark.bottomAnchor.constraint(equalTo: bottomAnchor),
])
let gesture = UITapGestureRecognizer(target: self, action: #selector(toggle))
self.addGestureRecognizer(gesture)
}
#objc func toggle() {
self.isChecked.toggle()
}
}
We've used auto-layout to keep the image view the same size as the view itself.
And, by implementing the var isChecked block we have a more "automated" way of setting the image view's hidden state.
In addition, we can now get the "state" of the checkbox in the controller like this:
if thisCheckBox.isChecked {
// do something
}
Now the view controller... we'll define an array of colors, loop through them to create the CheckBox objects, and add them to the stack view:
class SettingsViewController: UIViewController {
var stackView: UIStackView = {
let stackView = UIStackView()
stackView.axis = .horizontal
// desired spacing
stackView.spacing = 12
stackView.alignment = .fill
stackView.distribution = .fillEqually
return stackView
}()
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = .white
self.view.addSubview(stackView)
stackView.translatesAutoresizingMaskIntoConstraints = false
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
// stack view Top constraint
stackView.topAnchor.constraint(equalTo: g.topAnchor, constant: 150.0),
// centered horizontally
stackView.centerXAnchor.constraint(equalTo: g.centerXAnchor),
// explicit Height
stackView.heightAnchor.constraint(equalToConstant: 30.0),
])
let colors: [UIColor] = [
.red, .green, .black, .gray,
.brown, .yellow, .purple, .orange,
]
// loop through the colors, creating a new
// CheckBox object for each color
// and add it to the stack view
colors.forEach { c in
let checkbox = CheckBox(frame: .zero, color: c)
// 1:1 aspect ratio
checkbox.widthAnchor.constraint(equalTo: checkbox.heightAnchor).isActive = true
stackView.addArrangedSubview(checkbox)
}
}
}
As you can see, we've eliminated the need for all of the individual
lazy var redCheckbox: CheckBox = { ...
lazy var greenCheckbox: CheckBox = { ...
// etc
code blocks.
I have been unable to figure out how to solve this issue of mine. I tried to follow the answer from this post. How to change UIView height based on elements inside it
Like the post answer says to do, I have:
set autolayout constraints between UIContainerView top to the UITextView top and UIContainerView bottom to the UITextView bottom (#1)
set height constraint on the text view (#2) and change its constant when resizing the text view (#3)
I have to do this all programmatically. I first set the frame for the container view and give it a specified height. I'm not sure if that is okay too. I also add (#1) in viewDidLoad and am unsure if that's correct.
The text view is not able to increase height either with the current constraints (it is able to if I remove the topAnchor constraint but the container view still doesn't change size).
class ChatController: UICollectionViewController, UICollectionViewDelegateFlowLayout, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
lazy var containerView: UIView = {
let containerView = UIView()
containerView.translatesAutoresizingMaskIntoConstraints = false
containerView.frame = CGRect(x: 0, y: 0, width: self.view.frame.width, height: self.view.frame.height * 0.075)
return containerView
}()
lazy var textView: UITextView = {
let textView = UITextView()
textView.text = "Enter message..."
textView.isScrollEnabled = false
textView.translatesAutoresizingMaskIntoConstraints = false
textView.delegate = self
return textView
}()
override func viewDidLoad() {
super.viewDidLoad()
...
textViewDidChange(self.textView)
addContainerSubViews()
(#1)
containerView.topAnchor.constraint(equalTo: self.textView.topAnchor, constant: -UIScreen.main.bounds.size.height * 0.075 * 0.2).isActive = true
containerView.bottomAnchor.constraint(equalTo: self.textView.bottomAnchor, constant: UIScreen.main.bounds.size.height * 0.075 * 0.2).isActive = true
}
func addContainerSubViews() {
let height = UIScreen.main.bounds.size.height
let width = UIScreen.main.bounds.size.width
let containerHeight = height * 0.075
...//constraints for imageView and sendButton...
containerView.addSubview(self.textView)
self.textView.leftAnchor.constraint(equalTo: imageView.rightAnchor, constant: width/20).isActive = true
self.textView.rightAnchor.constraint(equalTo: sendButton.leftAnchor, constant: -width/20).isActive = true
(#2)
self.textView.heightAnchor.constraint(equalToConstant: containerHeight * 0.6).isActive = true
}
override var inputAccessoryView: UIView? {
get {
return containerView
}
}
(#3)
func textViewDidChange(_ textView: UITextView) {
let size = CGSize(width: view.frame.width, height: .infinity)
let estimatedSize = textView.sizeThatFits(size)
textView.constraints.forEach { (constraint) in
if constraint.firstAttribute == .height {
constraint.constant = estimatedSize.height
}
}
}
You can do this all with auto-layout / constraints. Because a UITextView with scrolling disabled will "auto-size" its height based on the text, no need to calculate height and change constraint constant.
Here's an example -- it's from a previous answer, modified to include your image view and send button:
class ViewController: 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 v = CustomInputAccessoryView()
v.editCallBack = { [weak self] str in
guard let self = self else { return }
self.editCallBack?(str)
}
v.doneCallBack = { [weak self] in
guard let self = self else { return }
self.resignFirstResponder()
}
return v
}()
}
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
}()
let imgView: UIImageView = {
let v = UIImageView()
v.contentMode = .scaleAspectFit
v.clipsToBounds = true
return v
}()
let sendButton: UIButton = {
let v = UIButton()
return v
}()
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = .lightGray
autoresizingMask = [.flexibleHeight, .flexibleWidth]
if let img = UIImage(named: "testImage") {
imgView.image = img
} else {
imgView.backgroundColor = .systemBlue
}
let largeConfig = UIImage.SymbolConfiguration(pointSize: 22, weight: .regular, scale: .large)
let buttonImg = UIImage(systemName: "paperplane.fill", withConfiguration: largeConfig)
sendButton.setImage(buttonImg, for: .normal)
[theTextView, imgView, sendButton].forEach { v in
addSubview(v)
v.translatesAutoresizingMaskIntoConstraints = false
}
// if we want to see the image view and button frames
//[imgView, sendButton].forEach { v in
// v.backgroundColor = .systemYellow
//}
NSLayoutConstraint.activate([
// constrain image view 40x40 with 8-pts leading
imgView.widthAnchor.constraint(equalToConstant: 40.0),
imgView.heightAnchor.constraint(equalTo: imgView.widthAnchor),
imgView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 8.0),
// constrain image view 40x40 with 8-pts trailing
sendButton.widthAnchor.constraint(equalToConstant: 40.0),
sendButton.heightAnchor.constraint(equalTo: sendButton.widthAnchor),
sendButton.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -8.0),
// constrain text view with 10-pts from
// image view trailing
// send button leading
theTextView.leadingAnchor.constraint(equalTo: imgView.trailingAnchor, constant: 10),
theTextView.trailingAnchor.constraint(equalTo: sendButton.leadingAnchor, constant: -10),
// constrain image view and button
// centered vertically
// at least 8-pts top and bottom
imgView.centerYAnchor.constraint(equalTo: centerYAnchor),
sendButton.centerYAnchor.constraint(equalTo: centerYAnchor),
imgView.topAnchor.constraint(greaterThanOrEqualTo: topAnchor, constant: 8.0),
sendButton.topAnchor.constraint(greaterThanOrEqualTo: topAnchor, constant: 8.0),
imgView.bottomAnchor.constraint(lessThanOrEqualTo: bottomAnchor, constant: -8.0),
sendButton.bottomAnchor.constraint(lessThanOrEqualTo: bottomAnchor, constant: -8.0),
// constrain text view 8-pts top/bottom
theTextView.topAnchor.constraint(equalTo: topAnchor, constant: 8.0),
theTextView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -8.0),
])
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
}
}
Output:
I'm trying to make a long text label, which lives inside of UI View, to multiple lines. I was searching for a solution for 2 hours, but I couldn't solve it, so I want to ask for some help. Here is the image of what I get now.
This is the View Hierarchy debugger.
In a table view cell, I put the repository name which I got from Github. In the third cell in the image, I want to make the repo name to two lines, since it's long. I saw this StackOverflow question, which sounds similar to my current problem and implemented in my code, but it didn't work.
Here is my code. It's too long but the point is, I have declared label.numberOfLines = 0 and label.adjustsFontSizeToFitWidth = true, and I tried to make my label convertible to multiple lines, but it didn't work. Could anyone please point me at what I'm doing wrong here?
import UIKit
class RepositoryCell: UITableViewCell {
//MARK: - Properties
let userImageView: UIImageView = {
let img = UIImageView()
img.contentMode = .scaleAspectFit
img.clipsToBounds = true
img.backgroundColor = .black
return img
}()
let containerView: UIView = {
let view = UIView()
view.clipsToBounds = true
view.backgroundColor = .systemPink
return view
}()
let userNameLabel: UILabel = {
let label = UILabel()
label.textColor = .black
label.font = UIFont.boldSystemFont(ofSize: 20)
label.numberOfLines = 0
label.adjustsFontSizeToFitWidth = true
return label
}()
let repositoryNameLabel: UILabel = {
let label = UILabel()
label.textColor = .gray
label.font = UIFont.systemFont(ofSize: 14)
label.numberOfLines = 0
label.adjustsFontSizeToFitWidth = true
return label
}()
let starNumLabel: UILabel = {
let label = UILabel()
label.textColor = .systemPink
label.font = UIFont.systemFont(ofSize: 14)
label.backgroundColor = .black
return label
}()
//MARK: - Init
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
addSubview(userImageView)
containerView.addSubview(userNameLabel)
containerView.addSubview(repositoryNameLabel)
addSubview(containerView)
addSubview(starNumLabel)
configureUserNameLabel()
configureRepositoryNameLabel()
configureViewConstraints()
}
override func layoutSubviews() {
super.layoutSubviews()
userImageView.layer.cornerRadius = userImageView.frame.height / 2
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
//MARK: - Helper functions
func configureCellView(repository: Repository) {
userImageView.image = UIImage(named: "001")
userNameLabel.text = repository.userName
repositoryNameLabel.text = repository.repositoryName
starNumLabel.text = "⭐️\(String(describing: repository.starNum))"
}
func configureUserNameLabel() {
userNameLabel.numberOfLines = 0
userNameLabel.adjustsFontSizeToFitWidth = true
}
func configureRepositoryNameLabel() {
repositoryNameLabel.numberOfLines = 0
repositoryNameLabel.adjustsFontSizeToFitWidth = true
}
func configureViewConstraints() {
setUserImageConstraints()
setContainerViewConstraints()
setUserNameLabelConstraints()
setRepositoryNameLabel()
setStarNumLabel()
}
func setUserImageConstraints() {
userImageView.translatesAutoresizingMaskIntoConstraints = false
userImageView.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true
userImageView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 10).isActive = true
userImageView.heightAnchor.constraint(equalToConstant: 70).isActive = true
userImageView.widthAnchor.constraint(equalTo: userImageView.heightAnchor, multiplier: 1).isActive = true
}
func setContainerViewConstraints() {
containerView.translatesAutoresizingMaskIntoConstraints = false
containerView.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true
containerView.topAnchor.constraint(equalTo: topAnchor, constant: 10).isActive = true
containerView.leadingAnchor.constraint(equalTo: userImageView.trailingAnchor, constant: 10).isActive = true
containerView.trailingAnchor.constraint(equalTo: starNumLabel.leadingAnchor, constant: -10).isActive = true
}
func setUserNameLabelConstraints() {
userNameLabel.translatesAutoresizingMaskIntoConstraints = false
userNameLabel.topAnchor.constraint(equalTo: containerView.topAnchor).isActive = true
userNameLabel.leadingAnchor.constraint(equalTo: containerView.leadingAnchor).isActive = true
// userNameLabel.trailingAnchor.constraint(equalTo: containerView.trailingAnchor).isActive = true
}
func setRepositoryNameLabel() {
repositoryNameLabel.translatesAutoresizingMaskIntoConstraints = false
repositoryNameLabel.topAnchor.constraint(equalTo: userNameLabel.bottomAnchor).isActive = true
repositoryNameLabel.leadingAnchor.constraint(equalTo: containerView.leadingAnchor).isActive = true
// repositoryNameLabel.trailingAnchor.constraint(equalTo: containerView.trailingAnchor).isActive = true
// this doesn't work...
// repositoryNameLabel.trailingAnchor.constraint(greaterThanOrEqualTo: containerView.leadingAnchor, constant: 5).isActive = true
}
func setStarNumLabel() {
starNumLabel.translatesAutoresizingMaskIntoConstraints = false
starNumLabel.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true
starNumLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -10).isActive = true
}
}
My guess is the trailing constraints of your label is not set properly.
You can use the View Hierarchy debugger in Xcode to have a clear view of the size your view actually has at runtime.
If the trailing anchor is not set, wrapping text isn't possible as it doesn't reach the "end" of the view.
How to resolve the cell height and based on the text dynamic text
My code:
class NotificationTableViewCell: UITableViewCell {
var notification:Notifications? {
didSet {
guard let notificationList = notification else {return}
if let title = notificationList.title{
titleLabel.text = title
}
if let message = notificationList.Message {
descriptionLabel.text = " \(message) "
}
}
}
let ContentView = UIViewFactory()
.build()
//MARK: - cell title Label
let titleLabel = CustomLabel(text: "")
.changeNumberOfLines(lines: 1)
.buildUI()
//MARK: - cell description Label
let descriptionLabel = CustomLabel(text: "")
.changeTextAlignment(.left)
.changeLineBreakMode(mode: .byWordWrapping)
.changeFont(12)
.buildUI()
//MARK: - Time Label
let timeLabel = CustomLabel(text: "")
.buildUI()
//MARK: - IDPal Logo
let logo : UIImageView = {
let imageView = UIImageView()
imageView.image = UIImage(named: "idpal")
imageView.translatesAutoresizingMaskIntoConstraints = false
return imageView
}()
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
logo.contentMode = .scaleAspectFit
selectionStyle = .none
setUp()
}
//MARK: - SetUpViews
func setUp() {
addSubview(logo)
addSubview(titleLabel)
addSubview(descriptionLabel)
setUpConstraints()
}
override func layoutSubviews() {
super.layoutSubviews()
let cornerRadius: CGFloat = 10
contentView.clipsToBounds = true
contentView.layer.masksToBounds = false
contentView.layer.shadowColor = UIColor(red: 0.74, green: 0.74, blue: 0.74, alpha: 0.50).cgColor
contentView.layer.cornerRadius = cornerRadius
contentView.backgroundColor = UIColor(red: 1.00, green: 1.00, blue: 1.00, alpha: 1)
contentView.layer.borderWidth = 1.0
contentView.layer.borderColor = UIColor.lightGray.cgColor
contentView.alpha = 1
contentView.frame = contentView.frame.inset(by: UIEdgeInsets(top: 2, left: 0, bottom: 0, right: 0))
//set the values for top,left,bottom,right margins
// let margins = UIEdgeInsets(top: 3, left: 0, bottom: 10, right: 0)
// contentView.frame = contentView.frame.inset(by: margins)
}
func setUpConstraints() {
// containerView.topAnchor.constraint(equalTo:topAnchor).isActive = true
// containerView.leftAnchor.constraint(equalTo:leftAnchor).isActive = true
// containerView.rightAnchor.constraint(equalTo:rightAnchor).isActive = true
// containerView.bottomAnchor.constraint(equalTo:bottomAnchor).isActive = true
//MARK: - IDPal Logo constraints
logo.topAnchor.constraint(equalTo:topAnchor,constant:10).isActive = true
logo.leftAnchor.constraint(equalTo:leftAnchor,constant:14).isActive = true
logo.widthAnchor.constraint(equalToConstant:30).isActive = true
logo.heightAnchor.constraint(equalToConstant:40).isActive = true
//MARK: - titleLabel constraints
titleLabel.topAnchor.constraint(equalTo:logo.topAnchor,constant:0).isActive = true
titleLabel.leftAnchor.constraint(equalTo:logo.rightAnchor,constant:10).isActive = true
titleLabel.rightAnchor.constraint(equalTo:rightAnchor).isActive = true
//MARK: - descriptionLabel constraints
descriptionLabel.topAnchor.constraint(equalTo: titleLabel.bottomAnchor).isActive = true
descriptionLabel.leftAnchor.constraint(equalTo: titleLabel.leftAnchor).isActive = true
descriptionLabel.bottomAnchor.constraint(equalTo: bottomAnchor,constant:10).isActive = true
descriptionLabel.rightAnchor.constraint(equalTo: titleLabel.rightAnchor).isActive = true
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
Hear my code can't able to configure if found any suggestion drop here please.
adding the border with it's to be space between contendview but not working for me.
I would like to give shadow effect like card similar to the image in my iOS app
thanks
how do i make custom UITextfield? i have created class with uitextfield as a subclass then i added label it shows up, but the border(UIView) are not showing up here is the code
import UIKit
class borderedTextField: UITextField{
let viewz:UIView! = UIView()
var border:UIView! = UIView()
var name:UILabel! = UILabel()
override func draw(_ rect: CGRect) {
self.addSubview(viewz)
}
override func layoutSubviews() {
backgroundColor = UIColor.green
}
func placement(){
border.frame.size.height = 15
border.backgroundColor = UIColor.black
border.translatesAutoresizingMaskIntoConstraints = false
viewz.addSubview(border)
border.heightAnchor.constraint(equalToConstant: 15).isActive = true
border.widthAnchor.constraint(equalToConstant: 100)
border.leftAnchor.constraint(equalTo: viewz.leftAnchor, constant: 10).isActive = true
border.bottomAnchor.constraint(equalTo: viewz.bottomAnchor, constant: -5).isActive = true
name.text = "whatt"
name.textAlignment = .left
name.textColor = UIColor.black
name.translatesAutoresizingMaskIntoConstraints = false
viewz.addSubview(name)
name.widthAnchor.constraint(equalToConstant: 100)
name.heightAnchor.constraint(equalToConstant: 50)
name.leftAnchor.constraint(equalTo: viewz.leftAnchor, constant: 5).isActive = true
name.topAnchor.constraint(equalTo: viewz.topAnchor, constant: 10).isActive = true
}
}
and i called it programatically (without storyboard) please let me know how to solve this problem without using storyboard i need it to be full code.
import UIKit
class signViewController: UIViewController,UITextFieldDelegate {
var animText:borderedTextField!
override func viewDidLoad() {
super.viewDidLoad()
let animText = borderedTextField()
animText.delegate = self
animText.placeholder = "click here"
animText.placement()
animText.frame = CGRect(x: 100, y: 500, width: 200, height: 90)
view.addSubview(animText)
}
}
You have to add init
override init (frame : CGRect) {
super.init(frame : frame)
self.placement()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.placement()
}
Also width of border is not active
border.widthAnchor.constraint(equalToConstant: 100).active = true
for name
name.widthAnchor.constraint(equalToConstant: 100).active = true
name.heightAnchor.constraint(equalToConstant: 50).active = true
Edit
border.leftAnchor.constraint(equalTo: self.leftAnchor, constant: 10).isActive = true
border.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: -5).isActive = true