UITextView removing interim blank lines on resize - ios

I have a UITextView that can potentially change size. Scrolling is disabled. Here what's happening:
Is there any way to prevent this strange behaviour but also keep the lineBreakMode to .byTruncatingTail? (removing this option produces the expected resize result)
The playground code so you can reproduce this easily:
import UIKit
import PlaygroundSupport
class HostController: UIViewController {
let textView = UITextView()
override func viewDidLoad() {
view.backgroundColor = .gray
textView.frame = CGRect(x: 10, y: 10, width: 150, height: 200)
textView.isEditable = false
textView.isScrollEnabled = false
textView.textContainer.lineBreakMode = .byTruncatingTail
textView.text = "Line 1\n\n\n\nLine 2\n\nLine 3\n\nLine 4\nSome text that goes beyond the width"
view.addSubview(textView)
let slider = UISlider(frame: CGRect(x: 160, y: 10, width: 100, height: 30))
slider.minimumValue = 10
slider.maximumValue = 200
slider.value = 200
slider.addTarget(self, action: #selector(sliderChanged(sender:)), for: .valueChanged)
view.addSubview(slider)
}
#objc func sliderChanged(sender: UISlider) {
textView.frame.size.height = CGFloat(sender.value)
}
}
let vc = HostController()
PlaygroundPage.current.liveView = vc

Turns out that if you add an empty space character between the new lines the problem is disappearing. For the example above, that would be:
textView.text = "Line 1\n \n \n \nLine 2\n \nLine 3\n \nLine 4\nSome text that goes beyond the width"

Related

Swift & UILabel : How to add padding and margin in Swift programmatically? [duplicate]

This question already has answers here:
Add padding between label and its border
(4 answers)
Closed 8 months ago.
I have created a text programmatically with a grey background using UILabel.
Now I would like to add padding to this paragraph/text. Also, it would be great if you could show me how to add margin to my UILabel as well.
import UIKit
final class SignUpViewController: UIViewController {
public let identifier = "Sign Up"
private let logoImage : UIImageView = {
let imageView = UIImageView()
imageView.layer.masksToBounds = true
imageView.contentMode = .scaleAspectFit
imageView.image = UIImage(named: "MyLogoWithTitle")
imageView.clipsToBounds = true
return imageView
}()
private let instructionText : UILabel = {
let label = UILabel()
label.text = "Please read terms and conditions below carefully before proceeding with the registration."
label.backgroundColor = UIColor().colorFromHex(hex: "#2C333C", opacity: 0.4)
label.numberOfLines = 0
label.tintColor = .white
return label
}()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
view.addSubview(logoImage)
view.addSubview(instructionText)
view.backgroundColor = UIColor().colorFromHex(hex: "#141920", opacity: 1.0)
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
logoImage.frame = CGRect(x: 0,
y: 0,
width: 140,
height: 60)
logoImage.center = CGPoint(x: view.center.x, y: view.height/5)
instructionText.frame = CGRect(
x: 5,
y: 5 + logoImage.bottom,
width: view.width - 20,
height: 50)
.integral
instructionText.layer.cornerRadius = 10
}
}
Notice that I created an extension to UIColor so that I can input hex color in this way - UIColor().colorFromHex(hex: "#2C333C", opacity: 0.4) .
I am looking forward to hearing from you. Thank you.
You can insert this UILabel into the container (any UIView) and set its position inside.
But the simplest trick is to use UIButton instead of UILabel. You can configure UIEdgeInsets for padding.
So that UIButton does not act as a button simply set button.isUserInteractionEnabled = false.
By default, text in the button are placed in the center, but its position is easy to change with contentHorizontalAlignment and contentVerticalAlignment
And as a bonus, you can add icons right near to the text. :)
UPD.
Could you give me a simple example? I tried that way but I didn't get the result I expected. – Punreach Rany
let buttonUsedAsLabel = UIButton()
// Your question was about padding
// It's it!
buttonUsedAsLabel.titleEdgeInsets = UIEdgeInsets(top: 5, left: 20, bottom: 5, right: 20)
// Make it not user interactable as UILabel is
buttonUsedAsLabel.isUserInteractionEnabled = false
// set any other properties
buttonUsedAsLabel.setTitleColor(.white, for: .normal)
buttonUsedAsLabel.contentVerticalAlignment = .top
buttonUsedAsLabel.contentHorizontalAlignment = .left
// Set title propeties AFTER it was created with text because it's nullable
// You can use attributed title also
// Never use any (button.titleLabel) before its creation
// for example: (button.titleLabel?.text = "zzz") do nothing here
buttonUsedAsLabel.setTitle("This is the text", for: .normal)
buttonUsedAsLabel.titleLabel?.font = .systemFont(ofSize: 20, weight: .medium)
buttonUsedAsLabel.titleLabel?.numberOfLines = 0
buttonUsedAsLabel.titleLabel?.lineBreakMode = .byWordWrapping
// and so on
// ...
// This is the triсk :)
Of course, you can do it with a storyboard if prefer.
1. Add this class
PaddingLabel.swift
import UIKit
class PaddingLabel: UILabel {
var edgeInset: UIEdgeInsets = .zero
override func drawText(in rect: CGRect) {
let insets = UIEdgeInsets.init(top: edgeInset.top, left: edgeInset.left, bottom: edgeInset.bottom, right: edgeInset.right)
super.drawText(in: rect.inset(by: insets))
}
override var intrinsicContentSize: CGSize {
let size = super.intrinsicContentSize
return CGSize(width: size.width + edgeInset.left + edgeInset.right, height: size.height + edgeInset.top + edgeInset.bottom)
}
}
2. Add this code to your ViewController
let label = PaddingLabel()
override func viewDidLoad() {
super.viewDidLoad()
label.backgroundColor = UIColor().colorFromHex(hex: "#2C333C", opacity: 0.4)
//Setting the padding label
label.edgeInset = UIEdgeInsets(top: 5, left: 10, bottom: 5, right: 10)
}
The answer to the link below is that I wrote the same content based on the storyboard.
Add padding between label and its border
I use textfield. Set padding and text in textfield. And do not allow editing.
extension UITextField {
func addLeftPadding() {
let paddingView = UIView(frame: CGRect(x: 0, y: 0, width: 12, height: self.frame.height))
self.leftView = paddingView
self.leftViewMode = ViewMode.always
}
}
//ViewController
#IBOutlet weak var myTextField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
myTextField.addLeftPadding()
myTextField.isUserInteractionEnabled = false
myTextField.text = "your label text"
}

iOS Swift Playgrounds Views Are Not Centered

So the picture says it all. I basically have a title and set the width of the label to the entire view's width and I have the text alignment to be centered. However, it's apparent that it's off to the right a little bit. I think that the playground view is cutting off a part of the view. Can anyone please tell me how to center the UILabel properly please? Thanks!
import UIKit
import PlaygroundSupport
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let title = UILabel(frame: CGRect(x: 0, y: 5, width: view.frame.width, height: 60))
title.textAlignment = .center
title.text = "Hello World!"
title.font = UIFont.systemFont(ofSize: 30, weight: .bold)
title.textColor = .white
self.view.addSubview(title)
}
}
PlaygroundPage.current.liveView = ViewController()
Setting view frames based on a view controller’s view’s frame in viewDidLoad will likely result in expected layout as auto layout has not completed its layout passes when viewDidLoad Is called. You should set your label’s frame from viewDidLayoutSubviews Instead. For example:
import UIKit
import PlaygroundSupport
class ViewController: UIViewController {
let titleLabel = UILabel(frame: .zero)
override func viewDidLoad() {
super.viewDidLoad()
titleLabel.textAlignment = .center
titleLabel.text = "Hello World!"
titleLabel.font = UIFont.systemFont(ofSize: 30, weight: .bold)
titleLabel.textColor = .white
self.view.addSubview(titleLabel)
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
titleLabel.frame = CGRect(x: 0, y: 5, width: view.frame.width, height: 60)
}
}
PlaygroundPage.current.liveView = ViewController()

Why setting titleView lowers header text?

I'm trying to dynamically set my navigation bar's text so that the header text always fits. I'm acccomplishing that like this:
// Pet's Day text "Joy's Day"
if let range = currentPet.range(of: "_") {
let petsName = currentPet[range.upperBound..<currentPet.endIndex]
let deviceWidth = UIScreen.main.bounds.size.width
let titleLabel = UILabel(frame: CGRect(x: 0, y: 0, width: deviceWidth, height: 40))
titleLabel.text = "\(petsName)'s Day"
titleLabel.font = UIFont.systemFont(ofSize: 30)
titleLabel.backgroundColor = UIColor.clear
titleLabel.textColor = UIColor.white
titleLabel.adjustsFontSizeToFitWidth = true
titleLabel.minimumScaleFactor = 0.5
titleLabel.textAlignment = .center
self.navBar.topItem?.titleView = titleLabel
}
However, as seen by this picture, this lowers the header text below its natural height:
The navigation bar on the left is from one of my app's other views, while the one on the right is the one I'm setting.
Both of these navigation bars are navigation bars that I've dragged in and made the prompt equal to an empty string to increase their height:
Can anybody please help me implement my code above so that it doesn't drop down the header text?
**Edit: Here are screenshots from Xcode's debug hierarchy:
This is the normal navigation bar:
This is the one I'm setting:
You need to set the baseline, by default is Align Baseline, you need to change to Align Centers
Code example:
class TestViewController: UIViewController {
#IBOutlet weak var navBar: UINavigationBar!
override func viewDidLoad() {
super.viewDidLoad()
// Change the height of the navbar
self.navBar.frame = CGRect(x: 0, y: 0, width: self.view.bounds.width, height: 68)
let deviceWidth = UIScreen.main.bounds.size.width
let titleLabel = UILabel(frame: CGRect(x: 0, y: 0, width: deviceWidth, height: 40))
titleLabel.text = "Log Events" // Change to Joy's Day and check the result
titleLabel.font = UIFont.systemFont(ofSize: 30)
titleLabel.baselineAdjustment = .alignBaselines
titleLabel.textColor = UIColor.white
titleLabel.adjustsFontSizeToFitWidth = true
titleLabel.minimumScaleFactor = 0.5
titleLabel.textAlignment = .center
self.navBar.topItem?.titleView = titleLabel
}
}

Initialize a button with a title from a Label in swift

I want to initialize a button's title from a Label. I have this code:
let smallSquare = CGSize(width: 30, height: 30)
let button = UIButton(frame: CGRect(origin: CGPointZero, size: smallSquare))
But, I do not know how I can initialize a title with my label:
let label = UILabel(frame: CGRectMake(0, 0, 200, 21))
label.center = CGPointMake(160, 284)
label.textAlignment = NSTextAlignment.Center
label.text = "I'am a test label"
Normally, I use this property to add a title with a string:
button.setTitle("Button Title",for: .normal)
Is it possible to simply put my Label in my button's title?
Thanks in advance
You can add you custom label as subview to your button like Mike Alter mentioned in the comments like this (Note: Code is in Swift 3, but should be easy to adopt to Swift 2.*. Hints are in the code comments):
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
let smallSquare = CGSize(width: 30, height: 30)
let button = UIButton(frame: CGRect(origin: .zero, size: smallSquare))
button.translatesAutoresizingMaskIntoConstraints = false
button.backgroundColor = .red
// add the button to your view
view.addSubview(button)
// set constraints of your button
button.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
button.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
let label = UILabel(frame: CGRect(x: 0, y: 0, width: 200, height: 21))
label.translatesAutoresizingMaskIntoConstraints = false
label.center = CGPoint(x: 160, y: 284)
label.textAlignment = .center
label.text = "I'm a test label"
// add the label to your button
button.addSubview(label)
// set constraints of your label
label.centerXAnchor.constraint(equalTo: button.centerXAnchor).isActive = true
label.centerYAnchor.constraint(equalTo: button.centerYAnchor).isActive = true
}
}
Result with your custom values looks like this (Just added the red background of the button so you see the frame of the button compared to the label frame):
Your label has a text property. You used it to set a value and you can also use it to get the value. The text is optional, so you need to unwrap.
let labelText = label.text ?? ""
button.setTitle(labelText, for: .normal)
if let textFromLabel = yourCustomLabel.text {
yourButton.setTitle(textFromLabel, .normal)
}
is what I'll suggest you to do

How to create UILabel programmatically using Swift?

How do I create a UILabel programmatically using Swift in Xcode 6?
I have started with a new "Single View Application" in Xcode 6 and selected Swift for this project. I have my files AppDelegate.swift and ViewController.swift and I'm not sure what to do from here.
Creating a UILabel programmatically in Swift 3+:
override func viewDidLoad() {
super.viewDidLoad()
let label = UILabel(frame: CGRect(x: 0, y: 0, width: 200, height: 21))
label.center = CGPoint(x: 160, y: 285)
label.textAlignment = .center
label.text = "I'm a test label"
self.view.addSubview(label)
}
Here is the correct code for Swift 3, with comments for instructional purposes:
override func viewDidLoad()
{
super.viewDidLoad()
// CGRectMake has been deprecated - and should be let, not var
let label = UILabel(frame: CGRect(x: 0, y: 0, width: 200, height: 21))
// you will probably want to set the font (remember to use Dynamic Type!)
label.font = UIFont.preferredFont(forTextStyle: .footnote)
// and set the text color too - remember good contrast
label.textColor = .black
// may not be necessary (e.g., if the width & height match the superview)
// if you do need to center, CGPointMake has been deprecated, so use this
label.center = CGPoint(x: 160, y: 284)
// this changed in Swift 3 (much better, no?)
label.textAlignment = .center
label.text = "I am a test label"
self.view.addSubview(label)
}
Just to add onto the already great answers, you might want to add multiple labels in your project so doing all of this (setting size, style etc) will be a pain. To solve this, you can create a separate UILabel class.
import UIKit
class MyLabel: UILabel {
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
initializeLabel()
}
override init(frame: CGRect) {
super.init(frame: frame)
initializeLabel()
}
func initializeLabel() {
self.textAlignment = .left
self.font = UIFont(name: "Halvetica", size: 17)
self.textColor = UIColor.white
}
}
To use it, do the following
import UIKit
class ViewController: UIViewController {
var myLabel: MyLabel()
override func viewDidLoad() {
super.viewDidLoad()
myLabel = MyLabel(frame: CGRect(x: self.view.frame.size.width / 2, y: self.view.frame.size.height / 2, width: 100, height: 20))
self.view.addSubView(myLabel)
}
}
Swift 4.X and Xcode 10
let lbl = UILabel(frame: CGRect(x: 10, y: 50, width: 230, height: 21))
lbl.textAlignment = .center //For center alignment
lbl.text = "This is my label fdsjhfg sjdg dfgdfgdfjgdjfhg jdfjgdfgdf end..."
lbl.textColor = .white
lbl.backgroundColor = .lightGray//If required
lbl.font = UIFont.systemFont(ofSize: 17)
//To display multiple lines in label
lbl.numberOfLines = 0 //If you want to display only 2 lines replace 0(Zero) with 2.
lbl.lineBreakMode = .byWordWrapping //Word Wrap
// OR
lbl.lineBreakMode = .byCharWrapping //Charactor Wrap
lbl.sizeToFit()//If required
yourView.addSubview(lbl)
If you have multiple labels in your class use extension to add properties.
//Label 1
let lbl1 = UILabel(frame: CGRect(x: 10, y: 50, width: 230, height: 21))
lbl1.text = "This is my label fdsjhfg sjdg dfgdfgdfjgdjfhg jdfjgdfgdf end..."
lbl1.myLabel()//Call this function from extension to all your labels
view.addSubview(lbl1)
//Label 2
let lbl2 = UILabel(frame: CGRect(x: 10, y: 150, width: 230, height: 21))
lbl2.text = "This is my label fdsjhfg sjdg dfgdfgdfjgdjfhg jdfjgdfgdf end..."
lbl2.myLabel()//Call this function from extension to all your labels
view.addSubview(lbl2)
extension UILabel {
func myLabel() {
textAlignment = .center
textColor = .white
backgroundColor = .lightGray
font = UIFont.systemFont(ofSize: 17)
numberOfLines = 0
lineBreakMode = .byCharWrapping
sizeToFit()
}
}
Create UILabel view outside viewDidLoad class and then add that view to your main view in viewDidLoad method.
lazy var myLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.text = "This is label view."
label.font = UIFont.systemFont(ofSize: 12)
return label
}()
And then add that view in viewDidLoad()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(myLabel)
// Set its constraint to display it on screen
myLabel.widthAnchor.constraint(equalToConstant: 200).isActive = true
myLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
myLabel.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
}
You can create a label using the code below. Updated.
let yourLabel: UILabel = UILabel()
yourLabel.frame = CGRect(x: 50, y: 150, width: 200, height: 21)
yourLabel.backgroundColor = UIColor.orange
yourLabel.textColor = UIColor.black
yourLabel.textAlignment = NSTextAlignment.center
yourLabel.text = "test label"
self.view.addSubview(yourLabel)
Another answer in Swift 3:
let myLabel = UILabel()
myLabel.frame = CGRect(x: 0, y: 0, width: 100, height: 100)
myLabel.center = CGPoint(x: 0, y: 0)
myLabel.textAlignment = .center
myLabel.text = "myLabel!!!!!"
self.view.addSubview(myLabel)
Create label in swift 4
let label = UILabel(frame: CGRect(x: self.view.frame.origin.x, y: self.view.frame.origin.y, width: self.view.frame.size.width, height: 50))
label.textAlignment = .center
label.text = "Hello this my label"
//To set the color
label.backgroundColor = UIColor.white
label.textColor = UIColor.black
//To set the font Dynamic
label.font = UIFont(name: "Helvetica-Regular", size: 20.0)
//To set the system font
label.font = UIFont.systemFont(ofSize: 20.0)
//To display multiple lines
label.numberOfLines = 0
label.lineBreakMode = .byWordWrapping //Wrap the word of label
label.lineBreakMode = .byCharWrapping //Wrap the charactor of label
label.sizeToFit()
self.view.addSubview(label)
An alternative using a closure to separate out the code into something a bit neater using Swift 4:
class theViewController: UIViewController {
/** Create the UILabel */
var theLabel: UILabel = {
let label = UILabel()
label.lineBreakMode = .byWordWrapping
label.textColor = UIColor.white
label.textAlignment = .left
label.numberOfLines = 3
label.font = UIFont(name: "Helvetica-Bold", size: 22)
return label
}()
override func viewDidLoad() {
/** Add theLabel to the ViewControllers view */
view.addSubview(theLabel)
}
override func viewDidLayoutSubviews() {
/* Set the frame when the layout is changed */
theLabel.frame = CGRect(x: 0,
y: 0,
width: view.frame.width - 30,
height: 24)
}
}
As a note, attributes for theLabel can still be changed whenever using functions in the VC. You're just setting various defaults inside the closure and minimizing clutter in functions like viewDidLoad()
Swift 4.2 and Xcode 10. Somewhere in ViewController:
private lazy var debugInfoLabel: UILabel = {
let label = UILabel()
label.textColor = .white
label.translatesAutoresizingMaskIntoConstraints = false
yourView.addSubview(label)
NSLayoutConstraint.activate([
label.centerXAnchor.constraint(equalTo: suggestionView.centerXAnchor),
label.centerYAnchor.constraint(equalTo: suggestionView.centerYAnchor, constant: -100),
label.widthAnchor.constraint(equalToConstant: 120),
label.heightAnchor.constraint(equalToConstant: 50)])
return label
}()
...
Using:
debugInfoLabel.text = debugInfo
let label = UILabel(frame: CGRect(x: 0, y: 0, width: 200, height: 21))
label.center = CGPoint(x: 160, y: 285)
label.textAlignment = .center
label.text = "My label"
self.view.addSubview(label)
Try above code in ViewDidLoad
Swift 4.2 and Xcode 10
Initialize label before viewDidLoad.
lazy var topLeftLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.text = "TopLeft"
return label
}()
In viewDidLoad add label to the view and apply constraints.
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(topLeftLabel)
topLeftLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 10).isActive = true
topLeftLabel.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 10).isActive = true
}

Resources