Why textView do not accommodate text kept inside it? - ios

I have a plain textView and text inside it. With boundingRect i determine size based on text property of UITextView. Then i apply it to the size of my cell of collectionView. But text do not lay completely inside textView. but if i add to the width of the retrieved size 10 points everything works ok
Maybe i did not take something into account when was applying size to the cell?
Here i created my textView inside cell Class:
class experimentalCell: BaseCellInAppStoreFolder {
lazy var familySharingView: UITextView = {
let tv = UITextView()
tv.text = "Family Sharing"
tv.textAlignment = .right
tv.backgroundColor = UIColor.green
tv.textContainer.maximumNumberOfLines = 1
tv.font = UIFont.systemFont(ofSize: 11)
let options = NSStringDrawingOptions.usesLineFragmentOrigin
let dummySize = CGSize(width: 1000, height: self.frame.height - 16)
let rect = tv.text?.boundingRect(with: dummySize, options: options, context: nil)
tv.textContainerInset = UIEdgeInsets(top: 8, left: 0, bottom: 8, right: 0)
return tv
}()
override func setupViews() {
super.setupViews()
addSubview(textView)
addConstraintsWithFormat("H:|[v0]|", views: textView)
addConstraintsWithFormat("V:|[v0]|", views: textView)
}
}
Then I applied size obtained form rect property to size of my cell.

Related

UIButton Image with PDF has full width image view

I'm trying to build a radio-style like button (a circle with a dot on the left, and text next to
it on the right), pretty much like this:
I have 2 PDFs (link to one of them) containing images for selected and unselected radios. My code for the button is as follows:
let radioBtn = UIButton()
radioBtn.setImage(UIImage(named: "radio", in: .module, compatibleWith: nil), for: .normal)
radioBtn.setImage(UIImage(named: "radio_ticked", in: .module, compatibleWith: nil), for: .selected)
radioBtn.contentHorizontalAlignment = .leading
radioBtn.titleLabel?.adjustsFontSizeToFitWidth = true
radioBtn.titleEdgeInsets = UIEdgeInsets(top: 0, left: 5, bottom: 0, right: 0)
radioBtn.contentEdgeInsets = UIEdgeInsets(top: 8, left: 0, bottom: 8, right: 0)
radioBtn.heightAnchor.constraint(equalToConstant: 40).isActive = true
radioBtn.contentVerticalAlignment = .center
radioBtn.imageView?.contentMode = .scaleAspectFit
The problem is that the UIImage stretches over the whole width (the blue part) and there is no space for thee text to show:
What I want to accomplish is the radio completely on the left, and then the text next to it with some inset. How can this be achieved?
First, your code is not setting a Width for your button. In that case, the button will set the width of its imageView to the size of the image -- with your pdf, that ends up being 510 pts wide.
So, couple options...
Use some scaling code to resize your image. If you're setting the button Height to 40, with 8-pts top and bottom insets, you need a 24x24 image.
Give your button a Width constraint, and calculate the imageView insets "on-the-fly."
Probably the easiest way to do that is with a UIButton subclass, such as this:
class RadioButton: UIButton {
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() -> Void {
contentHorizontalAlignment = .leading
// 8-pts inset "padding" on all 4 sides
contentEdgeInsets = UIEdgeInsets(top: 8, left: 8, bottom: 8, right: 8)
// set title inset Left to content inset Left
var insets: UIEdgeInsets = titleEdgeInsets
insets.left = contentEdgeInsets.left
titleEdgeInsets = insets
// set images for .normal and .selected
if let img = UIImage(named: "radio") {
setImage(img, for: .normal)
}
if let img = UIImage(named: "radio_ticked") {
setImage(img, for: .selected)
}
}
override func layoutSubviews() {
super.layoutSubviews()
// make sure an image was set (otherwise, there is no imageView)
if let imgView = imageView {
// get height of imageView
let h = imgView.frame.height
// get current (default) image edge insets
var insets: UIEdgeInsets = imageEdgeInsets
// set inset Right to width of self minus imageView Height + Left and Right content insets
insets.right = bounds.width - (h + contentEdgeInsets.left + contentEdgeInsets.right)
// update image edge insets
imageEdgeInsets = insets
}
}
}
example in use:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let radioBtn = RadioButton()
// background color so we can see its frame
radioBtn.backgroundColor = .red
radioBtn.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(radioBtn)
// give it a 22-pt bold font
radioBtn.titleLabel?.font = .boldSystemFont(ofSize: 22.0)
// set the Title
radioBtn.setTitle("Testing", for: [])
// Height: 40
radioBtn.heightAnchor.constraint(equalToConstant: 40).isActive = true
// Width: 240
radioBtn.widthAnchor.constraint(equalToConstant: 240.0).isActive = true
// centered in view
radioBtn.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
radioBtn.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
}
}
Result:

Swift: Make vertical scrolling feed with programmatically sized UIViews

I'm building a scrolling feed in my app with data grabbed from a database (firebase). I'm not very experienced in Swift as most of the stuff I do is web design. What I'm looking for is a good way to size the height of my UIViews. Here is what I currently have (fixed height):
Here's my code UIView class:
class eventView: UIView {
let eventDate : UILabel = {
let eventDate = UILabel()
eventDate.translatesAutoresizingMaskIntoConstraints = false
eventDate.numberOfLines = 0
eventDate.textAlignment = .center
return eventDate
}()
let eventTitle : UILabel = {
Same thing as eventDate
}()
let eventDesc : UILabel = {
same thing as eventDate
}()
override init(frame: CGRect) {
super.init(frame: frame)
self.backgroundColor = UIColor.lightGray
self.layer.cornerRadius = 10
self.addSubview(eventDate)
eventDate.leftAnchor.constraint(equalTo: self.leftAnchor).isActive = true
eventDate.rightAnchor.constraint(equalTo: self.rightAnchor).isActive = true
self.addSubview(eventTitle)
eventTitle.leftAnchor.constraint(equalTo: self.leftAnchor).isActive = true
eventTitle.rightAnchor.constraint(equalTo: self.rightAnchor).isActive = true
eventTitle.topAnchor.constraint(equalTo: eventDate.bottomAnchor).isActive = true
self.addSubview(eventDesc)
eventDesc.leftAnchor.constraint(equalTo: self.leftAnchor).isActive = true
eventDesc.rightAnchor.constraint(equalTo: self.rightAnchor).isActive = true
eventDesc.topAnchor.constraint(equalTo: eventTitle.bottomAnchor).isActive = true
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Here is my for list that displays these UIViews:
var i = 0
for data in self.eventViewData {
let view = eventView(frame: CGRect(x: 0, y: ( CGFloat(170 * i)), width: self.scrollView.frame.width - 20, height: CGFloat(150)))
view.center.x = self.scrollView.center.x
view.eventDate.text = data.date
view.eventTitle.text = data.title
view.eventDesc.text = data.description
self.scrollView.addSubview(view)
i += 1
}
I usually am using HTML div's and such so I'm having a hard time figuring out how to style these. Any information or links to tutorials on how to programmatically adjust constraints to the UILabels in my eventViews are also appreciated.
How to calculate the desired height seems to be the question. While there are a lot of ways to do this one approach would be something like this:
func heightForView(text:String, font:UIFont, width:CGFloat) -> CGFloat{
let label:UILabel = UILabel(frame: CGRectMake(0, 0, width, CGFloat.greatestFiniteMagnitude))
label.numberOfLines = 0
label.lineBreakMode = NSLineBreakMode.byWordWrapping
label.font = font
label.text = text
label.sizeToFit()
return label.frame.height
}
let font = UIFont(name: "Helvetica", size: 20.0)
var height = heightForView("This is just a load of text", font: font, width: 100.0)
// apply height to desired view
I prefer to put the heightForView function in an extension myself but it isn't required.
I assumed you're using table view, why not do dynamic height? Good reference https://www.raywenderlich.com/1067-self-sizing-table-view-cells
In essense, you use auto layout to define your top and bottom constraint accordingly for every element in your cell, and your table view delegation/data source method for heightforrow and estimatedheightforrow, return UITableViewAutomaticDimension

Swift iOS - How to line up a sublayer position to match the center of button text

I have a button that is text. I added a red background subLayer and I made the backgroundLayer's width and height bigger then the button text. I tried to center the background layer to the button using:
backgroundLayer.position = button.center
It's not centering. This is what I get:
I know I can set the background color and cornerRadius on the button directly but when I do it that way the red background hugs the text:
button.backgroundColor = UIColor.red
button.layer.cornerRadius = 10
I want the redbackground to be wider and taller then the text:
backgroundLayer.frame = CGRect(x: 0, y: 0, width: buttonTextSize.width + 10, height: buttonTextSize.height + 5
I would Photoshop an example but I don't have Photshop in front of me at the moment. This is the closest I can find. This is a button from Vimeo. They aren't using text but the backgroundLayer is much wider and taller then the button image and the backgroundLayer's position is aligned with the button's midX and midY:
How do I get the position of the background subLayer to line up with the center of the button's text?
let button: UIButton = {
let button = UIButton()
button.translatesAutoresizingMaskIntoConstraints = false
button.setTitle("Next", for: .normal)
button.setTitleColor(UIColor.white, for: .normal)
button.contentHorizontalAlignment = .center
button.titleLabel?.font = UIFont.systemFont(ofSize: 23)
return button
}()
let backgroundLayer: CALayer = {
let layer = CALayer()
layer.backgroundColor = UIColor.red.cgColor
layer.cornerRadius = 10
return layer
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor.lightGray
view.addSubview(button)
button.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
button.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
}
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
let buttonText = button.titleLabel?.text
let buttonTextSize = (buttonText! as NSString).size(withAttributes: [NSAttributedStringKey.font: UIFont.systemFont(ofSize: 23.0)])
// I added 10 points to the backgroundLayer's width and 5 points to the backgroundLayer's height so its wider then the text
backgroundLayer.frame = CGRect(x: 0, y: 0, width: buttonTextSize.width + 10, height: buttonTextSize.height + 5)
button.layer.insertSublayer(backgroundLayer, at: 0)
backgroundLayer.position = button.center
}
Here's a button that seems to look the way you want (of course you can adjust any parameters that don't suit your sensibilities):
This button is automatically red, corner-rounded, and considerably larger than its text (even when the button is positioned using auto layout).
Here's how it was achieved through a subclass:
class MyRedButton : UIButton {
override func layoutSubviews() {
super.layoutSubviews()
self.backgroundColor = .red
self.layer.cornerRadius = 10
}
override var intrinsicContentSize: CGSize {
var sz = super.intrinsicContentSize
sz.width += 30; sz.height += 30
return sz
}
}
Matt's upvoted answer is correct.
Two things he pointed out to me in the comments that I was initially doing wrong was.
I tried to set backgroundLayer.position = button.center. This is wrong because the button's center is based on the frame's center and not it's bounds center. I should've set the backgroundLayer.position to match the center of the button's bounds
I tried to set the backgroundLayer's position to the button's center in viewWillLayoutSubviews which he said the button's bounds weren't known yet so the backgroundLayer had no information to base it on. I was supposed to add the code to viewDidLayoutSubviews
Here's the code here:
// 1. add the code to viewDidLayoutSubviews
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
let text = button.titleLabel?.text
let textSize = (text! as NSString).size(withAttributes: [NSAttributedStringKey.font: UIFont.systemFont(ofSize: 23.0)])
backgroundLayer.frame = CGRect(x: 0, y: 0, width: textSize.width + 10, height: textSize.height + 5)
button.layer.insertSublayer(backgroundLayer, at: 0)
// 2. get the buttons bound's center by accessing it's midX and midY
let buttonMidX = button.bounds.midX
let buttonMidY = button.bounds.midY
let buttonBoundsCenter = CGPoint(x: buttonMidX, y: buttonMidY)
// 3. set the backgroundLayer's postion to the buttonBoundsCenter
backgroundLayer.position = buttonBoundsCenter
}
And it works:

Wrapping an iOS UILabel as a block within a constrained UIView

I have a couple of UILabels within an UIView.
I constrain that containing view to 100px. If both the UILabels have an intrinsic width of 75px each (because of their content) what I would like is that the second label drops below the first because it cannot display without wrapping it's own text.
Is there a containing View in iOS that would support that behaviour?
Here is one example of "wrapping" labels based on fitting into the width of a parent view.
You can run this directly in a Playground page... Tap the red "Tap Me" button to toggle the text in the labels, to see how they "fit".
import UIKit
import PlaygroundSupport
// String extension for easy text width/height calculations
extension String {
func height(withConstrainedWidth width: CGFloat, font: UIFont) -> CGFloat {
let constraintRect = CGSize(width: width, height: .greatestFiniteMagnitude)
let boundingBox = self.boundingRect(with: constraintRect, options: .usesLineFragmentOrigin, attributes: [.font: font], context: nil)
return ceil(boundingBox.height)
}
func width(withConstrainedHeight height: CGFloat, font: UIFont) -> CGFloat {
let constraintRect = CGSize(width: .greatestFiniteMagnitude, height: height)
let boundingBox = self.boundingRect(with: constraintRect, options: .usesLineFragmentOrigin, attributes: [.font: font], context: nil)
return ceil(boundingBox.width)
}
}
class TestViewController : UIViewController {
let btn: UIButton = {
let b = UIButton()
b.translatesAutoresizingMaskIntoConstraints = false
b.setTitle("Tap Me", for: .normal)
b.backgroundColor = .red
return b
}()
let cView: UIView = {
let v = UIView()
v.translatesAutoresizingMaskIntoConstraints = false
v.backgroundColor = .blue
return v
}()
let labelA: UILabel = {
let v = UILabel()
// we will be explicitly setting the label's frame
v.translatesAutoresizingMaskIntoConstraints = true
v.backgroundColor = .yellow
return v
}()
let labelB: UILabel = {
let v = UILabel()
// we will be explicitly setting the label's frame
v.translatesAutoresizingMaskIntoConstraints = true
v.backgroundColor = .cyan
return v
}()
// spacing between labels when both fit on one line
let spacing: CGFloat = 8.0
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(btn)
btn.addTarget(self, action: #selector(didTap(_:)), for: .touchUpInside)
btn.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
btn.topAnchor.constraint(equalTo: view.topAnchor, constant: 20.0).isActive = true
// add the "containing" view
view.addSubview(cView)
// add the two labels to the containing view
cView.addSubview(labelA)
cView.addSubview(labelB)
// constrain containing view Left-20, Top-20 (below the button)
cView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20.0).isActive = true
cView.topAnchor.constraint(equalTo: btn.bottomAnchor, constant: 20.0).isActive = true
// containing view has a fixed width of 100
cView.widthAnchor.constraint(equalToConstant: 100.0).isActive = true
// constrain bottom of containing view to bottom of labelB (so the height auto-sizes)
cView.bottomAnchor.constraint(equalTo: labelB.bottomAnchor, constant: 0.0).isActive = true
// initial text in the labels - both will fit "on one line"
labelA.text = "First"
labelB.text = "Short"
}
func updateLabels() -> Void {
// get the label height based on its font
if let h = labelA.text?.height(withConstrainedWidth: CGFloat.greatestFiniteMagnitude, font: labelA.font) {
// get the calculated width of each label
if let wA = labelA.text?.width(withConstrainedHeight: h, font: labelA.font),
let wB = labelB.text?.width(withConstrainedHeight: h, font: labelB.font) {
// labelA frame will always start at 0,0
labelA.frame = CGRect(x: 0.0, y: 0.0, width: wA, height: h)
// will both labels + spacing fit in the containing view's width?
if wA + wB + spacing <= cView.frame.size.width {
// yes, so place labelB to the right of labelA (put them on "one line")
labelB.frame = CGRect(x: wA + spacing, y: 0.0, width: wB, height: h)
} else {
// no, so place labelB below labelA ("wrap" labelB "to the next line")
labelB.frame = CGRect(x: 0.0, y: h, width: wB, height: h)
}
}
}
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
updateLabels()
}
#objc func didTap(_ sender: Any?) -> Void {
// toggle labelB's text
if labelB.text == "Short" {
labelB.text = "Longer text"
} else {
labelB.text = "Short"
}
// adjust size / position of labels
updateLabels()
}
}
let vc = TestViewController()
vc.view.backgroundColor = .white
PlaygroundPage.current.liveView = vc

How to create dynamic label size programmatically in swift?

I'm trying to create a label programmatically in Swift but the issue I'm having is that based on the data model the amount of text can change thereby changing the size of the label. Typically I would create the label like this before knowing the text:
headerLabel.frame = CGRect(x: 0, y: 0, width: screenSize.width/2.5, height: screenSize.height/45)
headerLabel.center = CGPoint(x: screenSize.width/2, y: 245)
But in this case the text can be any amount ranging from a line to a paragraph so hard coding the height won't work. How to create the label so it would accommodate any amount of text?
You can access label intrinsic property after you have given the text to label then you can give the frame.
Swift 3:
let label = UILabel()
label.text = "Your text here"
label.textAlignment = .center
label.font = UIFont.systemFont(ofSize: 14)
label.frame = CGRect(x:0,y:0,width:label.intrinsicContentSize.width,height:label.intrinsicContentSize.height)
you can check some conditions based on the intrinsicContentSize.
Hope it helps.
This label has a defined width but not a defined height. The height is determined by the amount of text in the label. If you removed the width, the label would not break into lines, which is not what you want. The sizeToFit() method called at the end sets the height of the label after it knows how many lines of text it needs to break into based on the label's text. If this isn't what you wanted, let me know.
let label = UILabel()
label.text = "scones"
label.numberOfLines = 0 // 0 = as many lines as the label needs
label.frame.origin.x = 32
label.frame.origin.y = 32
label.frame.size.width = view.bounds.width - 64
label.font = UIFont.displayHeavy(size: 17) // my UIFont extension
label.textColor = UIColor.black
label.sizeToFit()
view.addSubview(label)
You can use the following to calculate the string height and width and then set them:
import Foundation
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let width = "Hello World".stringWidth // 74.6
let height = "Hello World".stringHeight // 16.7
let headerLabel = UILabel()
headerLabel.frame = CGRect(x: 0, y: 0, width: width, height: height)
headerLabel.center = CGPoint(x: screenSize.width/2, y: 245)
}
}
extension String {
var stringWidth: CGFloat {
let constraintRect = CGSize(width: UIScreen.main.bounds.width, height: .greatestFiniteMagnitude)
let boundingBox = self.trimmingCharacters(in: .whitespacesAndNewlines).boundingRect(with: constraintRect, options: [.usesLineFragmentOrigin, .usesFontLeading], attributes: [NSAttributedStringKey.font: UIFont.systemFont(ofSize: 14)], context: nil)
return boundingBox.width
}
var stringHeight: CGFloat {
let constraintRect = CGSize(width: UIScreen.main.bounds.width, height: .greatestFiniteMagnitude)
let boundingBox = self.trimmingCharacters(in: .whitespacesAndNewlines).boundingRect(with: constraintRect, options: [.usesLineFragmentOrigin, .usesFontLeading], attributes: [NSAttributedStringKey.font: UIFont.systemFont(ofSize: 14)], context: nil)
return boundingBox.height
}
}

Resources