UIView Expand to Content inside - ios

I have a function that returns a UIView. I want to dynamically change the size of my UIView(fullView) based on how much text is shown in the UITextView (thirdTextView). I currently have my height set at 200. How can I achieve a 'dynamic height'?
EDIT: Updated code so far. Is only working when a subView is added to the View. I don't want this...
func returnView(text1: String, text2: String) -> UIView {
let fullView = UIView()
fullView.backgroundColor = UIColor(red:0.82, green:0.83, blue:0.85, alpha:1.0)
fullView.translatesAutoresizingMaskIntoConstraints = false
let firstButton = UIButton()
firstButton.translatesAutoresizingMaskIntoConstraints = false
let secondButton = UIButton()
secondButton.translatesAutoresizingMaskIntoConstraints = false
let thirdTextView = UITextView()
thirdTextView.translatesAutoresizingMaskIntoConstraints = false
thirdTextView.text = "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book."
thirdTextView.backgroundColor = .clear
thirdTextView.isScrollEnabled = false
firstButton.setTitle("Button1", for: .normal)
firstButton.setTitleColor(.black, for: .normal)
firstButton.titleLabel?.font = UIFont.systemFont(ofSize: 15, weight: UIFont.Weight.medium)
firstButton.contentHorizontalAlignment = .left
secondButton.setTitle("Button2", for: .normal)
secondButton.setTitleColor(.black, for: .normal)
secondButton.titleLabel?.font = UIFont.systemFont(ofSize: 15, weight: UIFont.Weight.medium)
secondButton.contentHorizontalAlignment = .right
let descriptionBarStackView = UIStackView(arrangedSubviews: [firstButton, UIView() ,secondButton])
descriptionBarStackView.translatesAutoresizingMaskIntoConstraints = false
descriptionBarStackView.axis = .horizontal
descriptionBarStackView.alignment = .fill
let viewWithStackViews = UIStackView(arrangedSubviews: [descriptionBarStackView, thirdTextView])
viewWithStackViews.translatesAutoresizingMaskIntoConstraints = false
viewWithStackViews.axis = .vertical
viewWithStackViews.layoutMargins = UIEdgeInsets(top: 15, left: 10, bottom: 10, right:10)
viewWithStackViews.isLayoutMarginsRelativeArrangement = true
fullView.addSubview(viewWithStackViews)
// view.addSubview(fullView)
fullView.leadingAnchor.constraint(equalTo: viewWithStackViews.leadingAnchor).isActive = true
fullView.trailingAnchor.constraint(equalTo: viewWithStackViews.trailingAnchor).isActive = true
fullView.topAnchor.constraint(equalTo: viewWithStackViews.topAnchor).isActive = true
fullView.bottomAnchor.constraint(equalTo: viewWithStackViews.bottomAnchor).isActive = true
fullView.widthAnchor.constraint(equalTo: view.widthAnchor, constant: -10).isActive = true
fullView.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
fullView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
fullView.layer.cornerRadius = 5
return fullView
}

Give delegate of textview to the class
on textViewDidChange delegate method of textview, give height
In your scenario,
func textViewDidChange(_ textView: UITextView) {
thirdTextView.contentSize.height = textView.contentSize.height
}

Just set the width anchor, and it will dynamically expand to content size for height.
thirdTextView.widthAnchor.constraint(equalToConstant: 128.0).isActive = true

Related

Center text vertically in line of NSMutableAttributedString

I'm using NSMutableAttributedString to setup text style. When I set .minimumLineHeight, it centre text in line to the bottom of line. I would like to customise somehow this alignment to centre text in line vertically.
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.minimumLineHeight = fontLineHeight // 24 for example
attributedText.addAttribute(.paragraphStyle, value: paragraphStyle, range: fullRange)
I want to get such result:
The problem with providing a lineHeight is that you override the default height-calculation-behaviour with a hardcoded value. Even if you've provided the font's line height as the value, it can vary dynamically based on provided content and it's best to leave this to auto layout (Refer https://stackoverflow.com/a/33278748/9293498)
Auto-layout has a solution for these issues most of the time. All you need is a proper constraint setup for your label (and lineSpacing in your scenario) which can enable it to scale automatically based on provided text with just-enough space required. Both NSAttributedString and String values should work
Here's a code sample I've written trying to simulate your requirement:
Views:
private let termsAndConditionsContainer: UIStackView = {
let container = UIStackView()
container.backgroundColor = .clear
container.spacing = 16
container.axis = .vertical
container.alignment = .leading
container.distribution = .fill
container.translatesAutoresizingMaskIntoConstraints = false
return container
}()
private let dataAgreementButton: UIButton = {
let button = UIButton(type: .custom)
button.setImage(UIImage(), for: .normal)
button.layer.borderColor = UIColor.gray.cgColor
button.layer.borderWidth = 0.5
button.layer.cornerRadius = 16
return button
}()
private let dataAgreementLabel: UILabel = {
let label = UILabel()
label.font = UIFont.systemFont(ofSize: 17, weight: .medium)
label.numberOfLines = 0
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
private let tosAgreementButton: UIButton = {
let button = UIButton(type: .custom)
button.setImage(UIImage(), for: .normal)
button.layer.borderColor = UIColor.gray.cgColor
button.layer.borderWidth = 0.5
button.layer.cornerRadius = 16
return button
}()
private let tosAgreementLabel: UILabel = {
let label = UILabel()
label.font = UIFont.systemFont(ofSize: 17, weight: .medium)
label.numberOfLines = 0
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
Functions:
private func attributedString(with text: String) -> NSAttributedString {
//Leave it to auto-layout to determine the line height
let attributedString = NSMutableAttributedString(string: text)
let style = NSMutableParagraphStyle()
style.lineSpacing = 8 // I've just declared the spacing between lines
attributedString.addAttribute(.paragraphStyle, value: style, range: NSRange(location: 0, length: attributedString.length))
return attributedString
}
private func generateRowContainer() -> UIStackView {
let container = UIStackView()
container.backgroundColor = .clear
container.spacing = 16
container.axis = .horizontal
container.alignment = .center
container.distribution = .fill
container.layer.borderWidth = 0.5
container.layer.borderColor = UIColor.green.cgColor
container.translatesAutoresizingMaskIntoConstraints = false
return container
}
And here's my constraint setup as I add the above views in viewDidLoad:
override func viewDidLoad() {
super.viewDidLoad()
let dataAgreementContainer = generateRowContainer()
dataAgreementContainer.addArrangedSubview(dataAgreementButton)
dataAgreementLabel.attributedText = attributedString(with: "I agree with the Information note regarding personal data processing")
dataAgreementContainer.addArrangedSubview(dataAgreementLabel)
termsAndConditionsContainer.addArrangedSubview(dataAgreementContainer)
let tosAgreementContainer = generateRowContainer()
tosAgreementContainer.addArrangedSubview(tosAgreementButton)
tosAgreementLabel.attributedText = attributedString(with: "I agree with terms and conditions")
tosAgreementContainer.addArrangedSubview(tosAgreementLabel)
termsAndConditionsContainer.addArrangedSubview(tosAgreementContainer)
view.addSubview(termsAndConditionsContainer)
NSLayoutConstraint.activate([
dataAgreementButton.widthAnchor.constraint(equalToConstant: 32),
dataAgreementButton.heightAnchor.constraint(equalToConstant: 32),
tosAgreementButton.widthAnchor.constraint(equalToConstant: 32),
tosAgreementButton.heightAnchor.constraint(equalToConstant: 32),
termsAndConditionsContainer.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 32),
termsAndConditionsContainer.trailingAnchor.constraint(lessThanOrEqualTo: view.trailingAnchor, constant: -32),
termsAndConditionsContainer.centerYAnchor.constraint(equalTo: view.centerYAnchor)
])
}
I got the below output:
In the above example, I've used a horizontal stack view to maintain the alignment of text and radio button, which seems efficient to me for your requirement. But the point is, as long as your label is in a properly constrained environment, you don't have the need to manually specify any form of its height.

Swift: Programmatic Autolayout broken by UNavigationController

I have inherited a UIKit app where my predecessor has written all the interface code by hand. I have a view controller, a simple login screen that works just fine, but when I add it to a UINavigationController it is oddly stretched. This doesn't make a lot of sense to me.
I feel like I must be missing some simple flag, or what do I need to do to make the Navcontroller play nice with this programmatic autolayout (which is hopefully my last ever)
// this a view controller extension
func apply(constraints: [NSLayoutConstraint]) {
translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate(constraints)
}
There's my handy dandy, seemingly sane utility for applying constraints. As an example, the nice little oval thing making my password textfield looks nice has these constraints set up.
// these constraints are being setup in the view controller
emailCapsuleView.apply(constraints: [
emailCapsuleView.topAnchor.constraint(equalTo: subBigTitle.bottomAnchor, constant: 24.0),
emailCapsuleView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
emailCapsuleView.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.8),
emailCapsuleView.heightAnchor.constraint(equalToConstant: Dimensions.inputFieldHeight)
])
Outside of a UINavigationController everything is fine,
but in a UINavigationController everythhing is super wide and broken (simulator shot)
The width is not the problem here - if I use a constant for the width, my subview is still off center despite having it's centerX sets to the view controller's view's center X.
Configure your navigation controller and after that try like this:
set your objects:
let emailTextfield: UITextField = {
let tf = UITextField()
tf.backgroundColor = .white
tf.layer.borderWidth = 1
tf.layer.borderColor = UIColor.lightGray.cgColor
tf.setPadding(left: 10, right: 10)// use my extension below
tf.attributedPlaceholder = NSAttributedString(string: "Email address", attributes: [.foregroundColor: UIColor.lightGray])
tf.layer.cornerRadius = 14
tf.clipsToBounds = true
tf.translatesAutoresizingMaskIntoConstraints = false
return tf
}()
let passTextfield: UITextField = {
let tf = UITextField()
tf.backgroundColor = .white
tf.layer.borderWidth = 1
tf.layer.borderColor = UIColor.lightGray.cgColor
tf.setPadding(left: 10, right: 10)// use my extension below
tf.attributedPlaceholder = NSAttributedString(string: "Password", attributes: [.foregroundColor: UIColor.lightGray])
tf.layer.cornerRadius = 14
tf.clipsToBounds = true
tf.translatesAutoresizingMaskIntoConstraints = false
return tf
}()
let loginButton: UIButton = {
let b = UIButton(type: .system)
b.backgroundColor = .black
b.setTitle("Save Image", for: .normal)
b.titleLabel?.font = .systemFont(ofSize: 16, weight: .semibold)
b.setTitleColor(.white, for: .normal)
b.layer.cornerRadius = 14
b.clipsToBounds = true
b.translatesAutoresizingMaskIntoConstraints = false
return b
}()
let subBigTitle: UILabel = {
let l = UILabel()
l.text = "Your Big Title"
l.font = .systemFont(ofSize: 30, weight: .regular)
l.textColor = .black
l.textAlignment = .center
l.translatesAutoresizingMaskIntoConstraints = false
return l
}()
Now in viewDidLoad set stackView, bigTitle label and constraints:
view.addSubview(subBigTitle)
subBigTitle.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true
subBigTitle.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor).isActive = true
subBigTitle.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor).isActive = true
subBigTitle.heightAnchor.constraint(equalToConstant: 60).isActive = true
let stackView = UIStackView(arrangedSubviews: [emailTextfield, passTextfield, loginButton])
stackView.axis = .vertical
stackView.distribution = .fillEqually
stackView.spacing = 10
stackView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(stackView)
stackView.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.8).isActive = true
stackView.heightAnchor.constraint(equalToConstant: 170).isActive = true
stackView.topAnchor.constraint(equalTo: subBigTitle.bottomAnchor, constant: 24).isActive = true
stackView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
to add text padding to your textfield use my extension:
extension UITextField {
func setPadding(left: CGFloat, right: CGFloat){
let paddingView = UIView(frame: CGRect(x: 0, y: 0, width: left, height: self.frame.size.height))
self.leftView = paddingView
self.leftViewMode = .always
let paddingViewRight = UIView(frame: CGRect(x: 0, y: 0, width: right, height: self.frame.size.height))
self.rightView = paddingViewRight
self.rightViewMode = .always
}
}
and this is the result:

How to set TextField in InputAccessoryView as First Responder [Swift]

Okay, so I have a tableView with a textField where when the user taps the textField within the tableView, the keyboard is presented with a custom InputAccessoryView. It looks like this:
And here is the code to create the custom InputAccessoryView, which I've tried within the cellForRowAt and on textFieldDidBeginEditing (as below) for the tableView (each row performs a different function).
func textFieldDidBeginEditing(_ textField: UITextField) {
print("textFieldDidBeginEditing")
if textField.tag == 1 {
profileDataTextField.addToolbarInputAccessoryView()
}
}
I've created it as an extension from the UITextField:
extension UITextField {
func addToolbarInputAccessoryView() {
let screenWidth = UIScreen.main.bounds.width
// Create Main Container View
let mainContainerView = UIView()
mainContainerView.backgroundColor = .clear
mainContainerView.frame = CGRect(x: 0, y: 0, width: screenWidth, height: 120)
// Create Heading Label
let label = UILabel()
label.textColor = .white
label.font = UIFont(name: "BrandonGrotesque-Bold", size: 20)
label.text = "Enter New Weight"
label.widthAnchor.constraint(equalToConstant: screenWidth).isActive = true
label.heightAnchor.constraint(equalToConstant: 30.0).isActive = true
label.textAlignment = .center
// Create Input Container View
let inputContainerView = UIView()
inputContainerView.backgroundColor = .white
inputContainerView.heightAnchor.constraint(equalToConstant: 80.0).isActive = true
inputContainerView.widthAnchor.constraint(equalToConstant: screenWidth).isActive = true
// Create inputTextField
let inputTextField = UITextField()
inputTextField.translatesAutoresizingMaskIntoConstraints = false
inputTextField.placeholder = "150"
inputTextField.textAlignment = .left
inputTextField.textColor = .darkGray
inputTextField.font = UIFont(name: "BrandonGrotesque-Bold", size: 50)
inputTextField.backgroundColor = .white
inputTextField.layer.cornerRadius = 4
inputTextField.layer.masksToBounds = true
inputTextField.borderStyle = .none
// Create metricTextField
let metricLabel = UILabel()
metricLabel.textColor = .darkGray
metricLabel.font = UIFont(name: "BrandonGrotesque-Bold", size: 18)
metricLabel.text = "lbs"
metricLabel.textAlignment = .left
// Create Done button
let doneButton = UIButton()
doneButton.backgroundColor = .systemIndigo
doneButton.setTitle("Done", for: .normal)
doneButton.titleLabel?.font = UIFont(name: "BrandonGrotesque-Bold", size: 20)
doneButton.cornerRadius = 4
doneButton.translatesAutoresizingMaskIntoConstraints = false
doneButton.addTarget(self, action: #selector(doneTapped), for: .touchUpInside)
// Create cancel button
let cancelButton = UIButton()
cancelButton.backgroundColor = .systemRed
cancelButton.setTitle("Cancel", for: .normal)
cancelButton.titleLabel?.font = UIFont(name: "BrandonGrotesque-Bold", size: 20)
cancelButton.cornerRadius = 4
cancelButton.translatesAutoresizingMaskIntoConstraints = false
cancelButton.addTarget(self, action: #selector(cancelTapped), for: .touchUpInside)
// Main Stack View
let mainStackView = UIStackView()
mainStackView.axis = .vertical
mainStackView.distribution = .fill
mainStackView.alignment = .fill
mainStackView.spacing = 10.0
mainStackView.backgroundColor = .clear
// Toolbar StackView
let toolbarStackView = UIStackView()
toolbarStackView.axis = .horizontal
toolbarStackView.distribution = .fillEqually
toolbarStackView.alignment = .fill
toolbarStackView.spacing = 20.0
toolbarStackView.backgroundColor = .white
// Input Stackview
let inputStackView = UIStackView()
inputStackView.axis = .horizontal
inputStackView.distribution = .equalCentering
inputStackView.alignment = .center
inputStackView.spacing = 5.0
inputStackView.backgroundColor = .white
// Put it all together
mainStackView.addArrangedSubview(label)
inputStackView.addArrangedSubview(inputTextField)
inputStackView.addArrangedSubview(metricLabel)
toolbarStackView.addArrangedSubview(cancelButton)
toolbarStackView.addArrangedSubview(inputStackView)
toolbarStackView.addArrangedSubview(doneButton)
toolbarStackView.translatesAutoresizingMaskIntoConstraints = false
inputContainerView.addSubview(toolbarStackView)
mainStackView.addArrangedSubview(inputContainerView)
mainStackView.translatesAutoresizingMaskIntoConstraints = false
toolbarStackView.leadingAnchor.constraint(equalTo: inputContainerView.leadingAnchor, constant: 15).isActive = true
toolbarStackView.trailingAnchor.constraint(equalTo: inputContainerView.trailingAnchor, constant: -15).isActive = true
toolbarStackView.topAnchor.constraint(equalTo: inputContainerView.topAnchor, constant: 15).isActive = true
toolbarStackView.bottomAnchor.constraint(equalTo: inputContainerView.bottomAnchor, constant: -15).isActive = true
mainContainerView.addSubview(mainStackView)
inputAccessoryView = mainContainerView
}
#objc func cancelTapped() {
self.resignFirstResponder()
}
#objc func doneTapped() {
self.resignFirstResponder()
}
}
The problem is, I don't know how to reference the textField ("150" in the image) within the InputAccessoryView when the keyboard appears to set it as the first responder and not the original textField.
I want the inputs from the keyboard to change the text in the TextField within the InputAccessoryView. Right now the original textField is still the first responder.
I've tried setting inputTextField to becomeFirstResponder upon creation within the function (cellForRowAt), but that's obviously too early, as the keyboard hasn't appeared yet.
I have researched the similar questions/answers, but none of them cover how to reference the textField within the InputAccessoryView--especially when coming from a tableViewCell.
Try calling the becomeFirstResponder asynchronously with a delay to allow the keyboard to initialise:
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
textField.becomeFirstResponder()
}
Firstly I support making it a custom view instead of an extension for easy access of the textField
Secondly to access it your current way add a tag to your textfield like this
inputTextField.tag = 33
Finally access the textField like this
profileDataTextField.addToolbarInputAccessoryView()
guard let field = profileDataTextField.inputAccessoryView.viewWithTag(33) else { return }
field.becomeFirstResponder()

UIView setting constraints

I am having trouble creating a UIView instance programmatically. I have a function that returns the view so that I can use it in an NSAttachment inside a UITextView.
This is what I am trying to achieve:
This is what I am getting in the simulator:
Code Below:
let fullView = UIView()
let firstButton = UIButton()
let secondButton = UIButton()
let thirdTextView = UITextView()
fullView.frame = CGRect(x: 0, y: 0, width: textView.frame.width, height: 90)
fullView.backgroundColor = UIColor(red:0.82, green:0.83, blue:0.85, alpha:1.0)
firstButton.setTitle(text1, for: .normal)
firstButton.setTitleColor(.black, for: .normal)
firstButton.titleLabel?.font = UIFont.systemFont(ofSize: 15, weight: UIFont.Weight.medium)
firstButton.contentHorizontalAlignment = .left
secondButton.setTitle("Button2", for: .normal)
secondButton.setTitleColor(.black, for: .normal)
secondButton.titleLabel?.font = UIFont.systemFont(ofSize: 15, weight: UIFont.Weight.medium)
secondButton.contentHorizontalAlignment = .right
thirdTextView.text = text2
let descriptionBarStackView = UIStackView()
descriptionBarStackView.axis = .horizontal
descriptionBarStackView.alignment = .fill
descriptionBarStackView.distribution = .fillProportionally
descriptionBarStackView.addArrangedSubview(firstButton)
descriptionBarStackView.addArrangedSubview(secondButton)
let viewWithStackViews = UIStackView()
viewWithStackViews.axis = .vertical
viewWithStackViews.alignment = .fill // .leading .firstBaseline .center .trailing .lastBaseline
viewWithStackViews.distribution = .fillEqually
viewWithStackViews.addArrangedSubview(descriptionBarStackView)
viewWithStackViews.addArrangedSubview(thirdTextView)
fullView.addSubview(viewWithStackViews)
descriptionBarStackView.translatesAutoresizingMaskIntoConstraints = false
thirdTextView.translatesAutoresizingMaskIntoConstraints = false
viewWithStackViews.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
viewWithStackViews.topAnchor.constraint(equalTo: fullView.topAnchor, constant: 5),
viewWithStackViews.leadingAnchor.constraint(equalTo: fullView.leadingAnchor, constant: 5),
viewWithStackViews.trailingAnchor.constraint(equalTo: fullView.trailingAnchor, constant: 5),
viewWithStackViews.bottomAnchor.constraint(equalTo: fullView.bottomAnchor, constant: 5),
])
Edit:
Check this updated method
func customView(){
let fullView = UIView()
let firstButton = UIButton()
let secondButton = UIButton()
let thirdTextView = UITextView()
fullView.frame = CGRect(x: 0, y: 0, width: mainView.frame.width, height: mainView.frame.height)
fullView.backgroundColor = UIColor(red:0.82, green:0.83, blue:0.85, alpha:1.0)
firstButton.setTitle("Button1", for: .normal)
firstButton.setTitleColor(.black, for: .normal)
firstButton.titleLabel?.font = UIFont.systemFont(ofSize: 15, weight: UIFont.Weight.medium)
firstButton.contentHorizontalAlignment = .left
firstButton.backgroundColor = .clear
secondButton.setTitle("Button2", for: .normal)
secondButton.setTitleColor(.black, for: .normal)
secondButton.titleLabel?.font = UIFont.systemFont(ofSize: 15, weight: UIFont.Weight.medium)
secondButton.contentHorizontalAlignment = .right
secondButton.backgroundColor = .clear
thirdTextView.text = "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum."
thirdTextView.backgroundColor = .clear
let descriptionBarStackView = UIStackView()
descriptionBarStackView.axis = .horizontal
descriptionBarStackView.alignment = .fill
descriptionBarStackView.distribution = .fillProportionally
descriptionBarStackView.isLayoutMarginsRelativeArrangement = true
descriptionBarStackView.directionalLayoutMargins = NSDirectionalEdgeInsets(top: 0, leading: 5, bottom: 0, trailing: 5)
descriptionBarStackView.addArrangedSubview(firstButton)
descriptionBarStackView.addArrangedSubview(secondButton)
let viewWithStackViews = UIStackView()
viewWithStackViews.axis = .vertical
viewWithStackViews.alignment = .fill // .leading .firstBaseline .center .trailing .lastBaseline
viewWithStackViews.distribution = .fill
viewWithStackViews.addArrangedSubview(descriptionBarStackView)
viewWithStackViews.addArrangedSubview(thirdTextView)
fullView.addSubview(viewWithStackViews)
descriptionBarStackView.translatesAutoresizingMaskIntoConstraints = false
thirdTextView.translatesAutoresizingMaskIntoConstraints = false
viewWithStackViews.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint(item: descriptionBarStackView, attribute: NSLayoutConstraint.Attribute.height, relatedBy: NSLayoutConstraint.Relation.equal, toItem: nil, attribute: NSLayoutConstraint.Attribute.notAnAttribute, multiplier: 1, constant: 30).isActive = true
NSLayoutConstraint.activate([
viewWithStackViews.topAnchor.constraint(equalTo: fullView.topAnchor, constant: 0),
viewWithStackViews.leadingAnchor.constraint(equalTo: fullView.leadingAnchor, constant: 0),
viewWithStackViews.trailingAnchor.constraint(equalTo: fullView.trailingAnchor, constant: 0),
viewWithStackViews.bottomAnchor.constraint(equalTo: fullView.bottomAnchor, constant: 0),
])
fullView.layer.cornerRadius = 5
mainView.addSubview(fullView)
}
here i have listed changes
mainView.frame.height full height based on main text view
firstButton.backgroundColor = .clear
secondButton.backgroundColor = .clear
thirdTextView.backgroundColor = .clear
margin for button
descriptionBarStackView.isLayoutMarginsRelativeArrangement = true
descriptionBarStackView.directionalLayoutMargins = NSDirectionalEdgeInsets(top: 0, leading: 5, bottom: 0, trailing: 5)
NSLayoutConstraint constraint zero to all in full view
fullView.layer.cornerRadius = 5
thats it here is result..
Tried to preserve your layout structure
class VisualTestViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
let fullView = UIView()
fullView.backgroundColor = UIColor(red:0.82, green:0.83, blue:0.85, alpha:1.0)
fullView.translatesAutoresizingMaskIntoConstraints = false
let firstButton = UIButton()
firstButton.translatesAutoresizingMaskIntoConstraints = false
let secondButton = UIButton()
secondButton.translatesAutoresizingMaskIntoConstraints = false
let thirdTextView = UITextView()
thirdTextView.translatesAutoresizingMaskIntoConstraints = false
thirdTextView.text = "lorem ipsumlorem ipsumlorem ipsumlorem ipsumlorem ipsumlorem ipsumlorem ipsumlorem ipsumlorem ipsum"
let text1 = "Button1"
firstButton.setTitle(text1, for: .normal)
firstButton.setTitleColor(.black, for: .normal)
firstButton.titleLabel?.font = UIFont.systemFont(ofSize: 15, weight: UIFont.Weight.medium)
firstButton.contentHorizontalAlignment = .left
secondButton.setTitle("Button2", for: .normal)
secondButton.setTitleColor(.black, for: .normal)
secondButton.titleLabel?.font = UIFont.systemFont(ofSize: 15, weight: UIFont.Weight.medium)
secondButton.contentHorizontalAlignment = .right
let descriptionBarStackView = UIStackView(arrangedSubviews: [firstButton, UIView() ,secondButton])
descriptionBarStackView.translatesAutoresizingMaskIntoConstraints = false
descriptionBarStackView.axis = .horizontal
descriptionBarStackView.alignment = .center
let viewWithStackViews = UIStackView(arrangedSubviews: [descriptionBarStackView, thirdTextView])
viewWithStackViews.translatesAutoresizingMaskIntoConstraints = false
viewWithStackViews.axis = .vertical
viewWithStackViews.layoutMargins = UIEdgeInsets(top: 5, left: 5, bottom: 5, right: 5)
viewWithStackViews.isLayoutMarginsRelativeArrangement = true
fullView.addSubview(viewWithStackViews)
fullView.leadingAnchor.constraint(equalTo: viewWithStackViews.leadingAnchor).isActive = true
fullView.trailingAnchor.constraint(equalTo: viewWithStackViews.trailingAnchor).isActive = true
fullView.topAnchor.constraint(equalTo: viewWithStackViews.topAnchor).isActive = true
fullView.bottomAnchor.constraint(equalTo: viewWithStackViews.bottomAnchor).isActive = true
view.addSubview(fullView)
fullView.widthAnchor.constraint(equalTo: view.widthAnchor, constant: -10).isActive = true
fullView.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
fullView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
thirdTextView.isScrollEnabled = false
thirdTextView.backgroundColor = .clear
}
}
You are setting
viewWithStackViews.distribution = .fillEqually
Because of .fillEqually the StackView will allocate equal height to the subviews. Thats why you are seeing buttons in half of viewWithStackViews and thirdTextView in remaining half.
So change it to:
viewWithStackViews.distribution = .fillProportionally
One more thing: your thirdTextView is not getting its height according to its content.
Try this to allow you thirdTextView to get height automatically. In case the text might be too long, set scrolledEnabled to true and set height contraint for the textView.
thirdTextView.translatesAutoresizingMaskIntoConstraints = true
thirdTextView.sizeToFit()
thirdTextView.scrollEnabled = false

How to send UIVisualEffectView behind UILabel when based on the label's text's width and height

I added a blur effect behind a label. The blur refuses to go behind the label. I tried all 3 of these separately:
label.insertSubview(backgroundBlur, at: 0)
label.addSubview(backgroundBlur)
label.sendSubview(toBack: backgroundBlur)
The thing is I need the width and height of the UIVisualEffectView blur to be based on the the size of the label's text. The text is dynamic. Both labels need to have their own individual backgroundBlur.
How can I get the UIVisualEffectView blur to go behind each indivdual label when it's also based on the label's text's width and height? There should be two labels with 2 backgroundBlurs behind them.
let backgroundBlur: UIVisualEffectView = {
let blur = UIVisualEffectView(effect: UIBlurEffect(style: UIBlurEffectStyle.dark))
blur.layer.cornerRadius = 6
blur.layer.masksToBounds = true
return blur
}()
let labelOne: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.font = UIFont.boldSystemFont(ofSize: 17)
label.textColor = UIColor.white
label.textAlignment = .center
label.sizeToFit()
label.numberOfLines = 0
return label
}()
let labelTwo: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.font = UIFont.boldSystemFont(ofSize: 17)
label.textColor = UIColor.white
label.textAlignment = .center
label.sizeToFit()
label.numberOfLines = 0
return label
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(labelOne)
view.addSubview(labelTwo)
labelOne.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
labelOne.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
labelTwo.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
labelTwo.topAnchor.constraint(equalTo: labelOne.bottomAnchor, constant: 16).isActive = true
putBlurEffectBehindLabel(backgroundBlur, labelOne)
putBlurEffectBehindLabel(backgroundBlur, labelTwo)
}
func putBlurEffectBehindLabel(_ blur: UIVisualEffectView, _ label: UILabel){
blur.frame = label.bounds
// tried these individually but nada
label.insertSubview(backgroundBlur, at: 0)
label.addSubview(backgroundBlur)
label.sendSubview(toBack: backgroundBlur)
blur.center = CGPoint(x: label.bounds.midX, y: label.bounds.midY)
}
you have to add UILabel to UIVisualEffectView
backgroundBlur.addSubview(labelOne)
backgroundBlur.addSubview(labelTwo)
add backgroundBlur and set constraint
then add labelOne,labelTwo to backgroundBlur with there constraint
Or Add all to UIView and connect them with constraint
don't forget translatesAutoresizingMaskIntoConstraints = false
Declaration:
let backgroundBlur: UIVisualEffectView = {
let blur = UIVisualEffectView(effect: UIBlurEffect(style:
UIBlurEffectStyle.dark))
blur.layer.cornerRadius = 6
blur.layer.masksToBounds = true
blur.translatesAutoresizingMaskIntoConstraints = false
return blur
}()
let labelOne: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
let labelTwo: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
ViewDidLoad
self.view.addSubview(backgroundBlur)
self.view.addSubview(labelOne)
self.view.addSubview(labelTwo)
labelOne.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
labelOne.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
labelTwo.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
labelTwo.topAnchor.constraint(equalTo: labelOne.bottomAnchor, constant: 16).isActive = true
labelOne.text = "asdhaskdas saldhlsahdsa laskdhsakhd asdlhkhsad"
labelTwo.text = "asdhaskdas saldhlsahdsa laskdhsakhd asdlhkhsad"
labelOne.numberOfLines = 0
labelTwo.numberOfLines = 0
backgroundBlur.topAnchor.constraint(equalTo: labelOne.topAnchor, constant: -8).isActive = true
backgroundBlur.bottomAnchor.constraint(equalTo: labelTwo.bottomAnchor, constant: 8).isActive = true
backgroundBlur.trailingAnchor.constraint(equalTo: labelOne.trailingAnchor, constant: 8).isActive = true
backgroundBlur.leadingAnchor.constraint(equalTo: labelOne.leadingAnchor, constant: -8).isActive = true
I had to add two UIVisualEffectView named:
backgroundBlurOne
backgroundBlurTwo
Based on #AbdelahadDarwish suggestion of adding the label to the blur instead of adding the blur to the label I was able to get the blur behind the label:
// this is added inside the putBlurEffectBehindLabel function
blur.contentView.addSubview(label)
Also inside the putBlurEffectBehindLabel function I got the size of the label's text using (text! as NSString).size(withAttributes: [NSAttributedStringKey.font: UIFont])
and then based the width and height of the UIVisualEffectView (the blur) off of that.
I then added the text I wanted for labelOne and backgroundBlurOne in viewDidLoad.
Then I added the text I wanted for labelTwo and the backgroundBlurTwo for it in viewDidAppear. I had to do that so I can use the height from backgroundBlurOne + a 16 point distance so labelTwo could be where I needed it to be from labelOne.
let backgroundBlurOne: UIVisualEffectView = {
let blur = UIVisualEffectView(effect: UIBlurEffect(style: UIBlurEffectStyle.dark))
blur.translatesAutoresizingMaskIntoConstraints = false
blur.layer.cornerRadius = 6
blur.layer.masksToBounds = true
blur.isUserInteractionEnabled = false
blur.backgroundColor = UIColor.black.withAlphaComponent(10)
return blur
}()
let backgroundBlurTwo: UIVisualEffectView = {
let blur = UIVisualEffectView(effect: UIBlurEffect(style: UIBlurEffectStyle.dark))
blur.translatesAutoresizingMaskIntoConstraints = false
blur.layer.cornerRadius = 6
blur.layer.masksToBounds = true
blur.isUserInteractionEnabled = false
blur.backgroundColor = UIColor.black.withAlphaComponent(10)
return blur
}()
let labelOne: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.font = UIFont.systemFont(ofSize: 17)
label.textColor = UIColor.white
label.textAlignment = .center
label.sizeToFit()
label.numberOfLines = 0
return label
}()
let labelTwo: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.font = UIFont.systemFont(ofSize: 17)
label.textColor = UIColor.white
label.textAlignment = .center
label.sizeToFit()
label.numberOfLines = 0
return label
}()
override func viewDidLoad() {
super.viewDidLoad()
labelOne.text = "Hello"
putBlurEffectBehindLabel(backgroundBlurOne, labelOne, yDistance: 0)
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
labelTwo.text = "Heloooooooooooooooooooooooo"
putBlurEffectBehindLabel(backgroundBlurTwo, labelTwo, yDistance: backgroundBlurOne.frame.height + 16)
}
func putBlurEffectBehindLabel(_ blur: UIVisualEffectView, _ label: UILabel, yDistance: CGFloat){
view.addSubview(blur)
blur.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
blur.centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: yDistance).isActive = true
blur.contentView.addSubview(label)
label.centerXAnchor.constraint(equalTo: blur.centerXAnchor).isActive = true
label.centerYAnchor.constraint(equalTo: blur.centerYAnchor).isActive = true
let text = label.text
let textSize = (text! as NSString).size(withAttributes: [NSAttributedStringKey.font: UIFont.systemFont(ofSize: 17)])
blur.widthAnchor.constraint(equalToConstant: textSize.width + 15).isActive = true
blur.heightAnchor.constraint(equalToConstant: textSize.height + 10).isActive = true
}

Resources