UIImage content mode aspectFit and bottom - ios

Is it possible to set the contentMode for my UIImage to .scaleAspectFit and .bottom simultaneously ?
This is how my image looks like at the moment:
UIImageView:
let nightSky: UIImageView = {
let v = UIImageView()
v.image = UIImage(named: "nightSky")
v.translatesAutoresizingMaskIntoConstraints = false
v.contentMode = .scaleAspectFit
return v
}()
Constraints:
nightSky.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
nightSky.centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: -120).isActive = true
nightSky.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 30).isActive = true
nightSky.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -30).isActive = true

Here is a custom class that allows Aspect Fit and Alignment properties.
It is marked #IBDesignable so you can see it in Storyboard / Interface Builder.
The #IBInspectable properties are:
Image
Horizontal Alignment
Vertical Alignment
Aspect Fill
Select the image as you would for a normal UIImageView.
Valid values for HAlign are "left" "center" "right" or leave blank for default (center).
Valid values for VAlign are "top" "center" "bottom" or leave blank for default (center).
"Aspect Fill" is On or Off (True/False). If True, the image will be scaled to Aspect Fill instead of Aspect Fit.
#IBDesignable
class AlignedAspectFitImageView: UIView {
enum HorizontalAlignment: String {
case left, center, right
}
enum VerticalAlignment: String {
case top, center, bottom
}
private var theImageView: UIImageView = {
let v = UIImageView()
return v
}()
#IBInspectable var image: UIImage? {
get { return theImageView.image }
set {
theImageView.image = newValue
setNeedsLayout()
}
}
#IBInspectable var hAlign: String = "center" {
willSet {
// Ensure user enters a valid alignment name while making it lowercase.
if let newAlign = HorizontalAlignment(rawValue: newValue.lowercased()) {
horizontalAlignment = newAlign
}
}
}
#IBInspectable var vAlign: String = "center" {
willSet {
// Ensure user enters a valid alignment name while making it lowercase.
if let newAlign = VerticalAlignment(rawValue: newValue.lowercased()) {
verticalAlignment = newAlign
}
}
}
#IBInspectable var aspectFill: Bool = false {
didSet {
setNeedsLayout()
}
}
var horizontalAlignment: HorizontalAlignment = .center
var verticalAlignment: VerticalAlignment = .center
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
commonInit()
}
func commonInit() -> Void {
clipsToBounds = true
addSubview(theImageView)
}
override func layoutSubviews() {
super.layoutSubviews()
guard let img = theImageView.image else {
return
}
var newRect = bounds
let viewRatio = bounds.size.width / bounds.size.height
let imgRatio = img.size.width / img.size.height
// if view ratio is equal to image ratio, we can fill the frame
if viewRatio == imgRatio {
theImageView.frame = newRect
return
}
// otherwise, calculate the desired frame
var calcMode: Int = 1
if aspectFill {
calcMode = imgRatio > 1.0 ? 1 : 2
} else {
calcMode = imgRatio < 1.0 ? 1 : 2
}
if calcMode == 1 {
// image is taller than wide
let heightFactor = bounds.size.height / img.size.height
let w = img.size.width * heightFactor
newRect.size.width = w
switch horizontalAlignment {
case .center:
newRect.origin.x = (bounds.size.width - w) * 0.5
case .right:
newRect.origin.x = bounds.size.width - w
default: break // left align - no changes needed
}
} else {
// image is wider than tall
let widthFactor = bounds.size.width / img.size.width
let h = img.size.height * widthFactor
newRect.size.height = h
switch verticalAlignment {
case .center:
newRect.origin.y = (bounds.size.height - h) * 0.5
case .bottom:
newRect.origin.y = bounds.size.height - h
default: break // top align - no changes needed
}
}
theImageView.frame = newRect
}
}
Using this image:
Here's how it looks with a 240 x 240 AlignedAspectFitImageView with background color set to yellow (so we can see the frame):
Properties can also be set via code. For example:
override func viewDidLoad() {
super.viewDidLoad()
let testImageView = AlignedAspectFitImageView()
testImageView.image = UIImage(named: "bkg640x360")
testImageView.verticalAlignment = .bottom
view.addSubview(testImageView)
// set frame / constraints / etc
testImageView.frame = CGRect(x: 40, y: 40, width: 240, height: 240)
}
To show the difference between "Aspect Fill" and "Aspect Fit"...
Using this image:
We get this result with Aspect Fill: Off and VAlign: bottom:
and then this result with Aspect Fill: On and HAlign: right:

Set the UIImageView's top layout constraint priority to lowest (i.e. 250) and it will handle it for you.

Related

How do you display an image at the maximum available space?

I check if the image is vertical or horizontal. If it is horizontal, I rotate it:
#IBOutlet private weak var img: UIImageView!
img.image = file.image
let imageSize = file.image?.size
let imgWidth = imageSize?.width ?? 0
let imgHeight = imageSize?.height ?? 0
if imgWidth > imgHeight {
print("IMG HORIZONTAL")
imgDetail.transform = imgDetail.transform.rotated(by: .pi / 2)
} else {
print("IMG VERTICAL")
}
But it leaves me a space around the image. I would like it to be at the maximum size of the UIImageView.
Try this, declare your imageView:
let YourImageView: UIImageView = {
let iv = UIImageView()
iv.image = UIImage(named: "myImage")?.withRenderingMode(.alwaysOriginal)
iv.backgroundColor = .clear
iv.contentMode = .scaleAspectFit
iv.isUserInteractionEnabled = true
iv.clipsToBounds = true
iv.translatesAutoresizingMaskIntoConstraints = false
return iv
}()
in viewDidLoad set if statement and call setupConstraints:
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
guard let imgWidth = YourImageView.image?.size.width else { return }
guard let imgHeight = YourImageView.image?.size.height else { return }
if imgWidth > imgHeight {
print("IMG HORIZONTAL")
guard let image = YourImageView.image else { return }
let newImage = image.rotate(radians: .pi / 2) // image rotation
YourImageView.image = newImage
} else {
print("IMG VERTICAL")
}
setupConstraints()
}
set up constraints
fileprivate func setupConstraints() {
view.addSubview(YourImageView)
YourImageView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
YourImageView.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
YourImageView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
YourImageView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
}
write image extension
extension UIImage {
func rotate(radians: Float) -> UIImage? {
var newSize = CGRect(origin: CGPoint.zero, size: self.size).applying(CGAffineTransform(rotationAngle: CGFloat(radians))).size
// Trim off the extremely small float value to prevent core graphics from rounding it up
newSize.width = floor(newSize.width)
newSize.height = floor(newSize.height)
UIGraphicsBeginImageContextWithOptions(newSize, false, self.scale)
guard let context = UIGraphicsGetCurrentContext() else { return UIImage()}
// Move origin to middle
context.translateBy(x: newSize.width/2, y: newSize.height/2)
// Rotate around middle
context.rotate(by: CGFloat(radians))
// Draw the image at its center
self.draw(in: CGRect(x: -self.size.width/2, y: -self.size.height/2, width: self.size.width, height: self.size.height))
let newImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return newImage
}
}
this is the result normal (without image rotate extension call) and rotated (with image rotate extension call):

Is there any way to change text layout begin position in iOS with core text?

I have a string :
12345678901234567890123456789012345678901234567890(...)
the default label will be layouting from left to right.
and I want to display this label with this kind of layout (and the will be no ellipsis):
the text layout begin not in the left , but starts in the middle
then the layout continue to the right
fill the left at last
How to do this
You can do this a few different ways...
two "normal" UILabel as subviews
CoreText
using two CATextLayer
Here's an example using CATextLayer. Based on your descriptions:
text begins at horizontal center, then "wraps" around to the left
no ellipses truncation
uses intrinsic size of the text/string
I'm calling the custom view subclass RightLeftLabelView:
class RightLeftLabelView: UIView {
public var text: String = ""
{
didSet {
setNeedsLayout()
invalidateIntrinsicContentSize()
}
}
public var font: UIFont = .systemFont(ofSize: 17.0)
{
didSet {
setNeedsLayout()
invalidateIntrinsicContentSize()
}
}
public var textColor: UIColor = .black
{
didSet {
setNeedsLayout()
}
}
private let leftTL = CATextLayer()
private let rightTL = CATextLayer()
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() {
clipsToBounds = true
[leftTL, rightTL].forEach { tl in
tl.contentsScale = UIScreen.main.scale
layer.addSublayer(tl)
}
}
override func layoutSubviews() {
super.layoutSubviews()
// get the size of the text, limited to a single line
let sz = font.sizeOfString(string: text, constrainedToWidth: .greatestFiniteMagnitude)
var r = CGRect(origin: .zero, size: CGSize(width: ceil(sz.width), height: ceil(sz.height)))
// start right text layer at horizontal center
r.origin.x = bounds.midX
rightTL.frame = r //.offsetBy(dx: r.width * 0.5, dy: 0.0)
// end left text layer at horizontal center
r.origin.x -= r.width
leftTL.frame = r //.offsetBy(dx: -r.width * 0.5, dy: 0.0)
[leftTL, rightTL].forEach { tl in
tl.string = text
tl.font = font
tl.fontSize = font.pointSize
tl.foregroundColor = textColor.cgColor
}
}
override var intrinsicContentSize: CGSize {
return font.sizeOfString(string: text, constrainedToWidth: .greatestFiniteMagnitude)
}
}
It uses this UIFont extension to get the size of the text:
extension UIFont {
func sizeOfString (string: String, constrainedToWidth width: Double) -> CGSize {
let attributes = [NSAttributedString.Key.font:self]
let attString = NSAttributedString(string: string,attributes: attributes)
let framesetter = CTFramesetterCreateWithAttributedString(attString)
return CTFramesetterSuggestFrameSizeWithConstraints(framesetter, CFRange(location: 0,length: 0), nil, CGSize(width: width, height: .greatestFiniteMagnitude), nil)
}
}
and an example controller:
class RightLeftLabelVC: UIViewController {
let sampleStrings: [String] = [
"0123456789",
"This is a good test of custom wrapping.",
"This is a good test of custom wrapping when the text is too long to fit.",
]
var sampleIDX: Int = 0
override func viewDidLoad() {
super.viewDidLoad()
let stack: UIStackView = {
let v = UIStackView()
v.axis = .vertical
v.alignment = .center
v.spacing = 2
v.translatesAutoresizingMaskIntoConstraints = false
return v
}()
view.addSubview(stack)
let safeG = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
// center the stack view
stack.centerYAnchor.constraint(equalTo: safeG.centerYAnchor),
// full width
stack.leadingAnchor.constraint(equalTo: safeG.leadingAnchor),
stack.trailingAnchor.constraint(equalTo: safeG.trailingAnchor),
])
let myTestViewA = RightLeftLabelView()
let myTestViewB = RightLeftLabelView()
let myTestViewC = RightLeftLabelView()
let actualA = UILabel()
let actualB = UILabel()
let actualC = UILabel()
stack.addArrangedSubview(infoLabel("UILabel"))
stack.addArrangedSubview(actualA)
stack.addArrangedSubview(infoLabel("Custom View"))
stack.addArrangedSubview(myTestViewA)
stack.addArrangedSubview(infoLabel("UILabel"))
stack.addArrangedSubview(actualB)
stack.addArrangedSubview(infoLabel("Custom View"))
stack.addArrangedSubview(myTestViewB)
stack.addArrangedSubview(infoLabel("UILabel"))
stack.addArrangedSubview(actualC)
stack.addArrangedSubview(infoLabel("Custom View"))
stack.addArrangedSubview(myTestViewC)
// some vertical spacing
stack.setCustomSpacing(32.0, after: myTestViewA)
stack.setCustomSpacing(32.0, after: myTestViewB)
stack.setCustomSpacing(32.0, after: myTestViewC)
// for convenience
let rlViews: [RightLeftLabelView] = [
myTestViewA, myTestViewB, myTestViewC
]
let labels: [UILabel] = [
actualA, actualB, actualC
]
let strings: [String] = [
"0123456789",
"This is a good test of custom wrapping.",
"This is an example test of custom wrapping when the text is too long to fit.",
]
// set various properties
var i: Int = 0
for (v, l) in zip(rlViews, labels) {
v.backgroundColor = .cyan
v.text = strings[i]
l.backgroundColor = .green
l.text = strings[i]
l.numberOfLines = 0
i += 1
}
}
func infoLabel(_ s: String) -> UILabel {
let v = UILabel()
v.text = s
v.font = .italicSystemFont(ofSize: 14.0)
return v
}
}
The result looks like this:

How to update a UILabel width dynamically without overloading the CPU

Labels in iOS are create like (1), no horizontal margin and no beauty at all.
I would like to create a label like in (2), curved edges and a margin left and right
The contents of this label is updated 2 times per second and its width must change dynamically.
So I have created this class
#IBDesignable
class BeautifulLabel : UILabel {
// private var internalRect : CGRect? = .zero
override func drawText(in rect: CGRect) {
let insets = UIEdgeInsets(top: marginTop,
left: marginLeft,
bottom: marginBottom,
right: marginRight)
super.drawText(in: rect.inset(by: insets))
}
#IBInspectable var cornerRadius: CGFloat = 0 {
didSet {
self.layer.cornerRadius = cornerRadius
self.layer.masksToBounds = cornerRadius > 0
}
}
#IBInspectable var marginTop: CGFloat = 0
#IBInspectable var marginBottom: CGFloat = 0
#IBInspectable var marginLeft: CGFloat = 0
#IBInspectable var marginRight: CGFloat = 0
override func layoutSubviews() {
super.layoutSubviews()
var bounds = self.bounds
bounds.size.width += marginLeft + marginRight
bounds.size.height += marginTop + marginBottom
self.bounds = bounds
}
This works but adjusting self.bounds inside layoutSubviews(), makes this method to be called again, resulting in a huge loop, CPU spike and memory leak.
Then I tried this:
override var text: String? {
didSet {
let resizingLabel = UILabel(frame: self.bounds)
resizingLabel.text = self.text
var bounds = resizingLabel.textRect(forBounds: CGRect(x: 0, y: 0, width: 500, height: 50), limitedToNumberOfLines: 1)
bounds.size.width += marginLeft + marginRight
bounds.size.height += marginTop + marginBottom
self.bounds = bounds
}
}
this simply does not work. Label is not adjusted to the proper size.
The label must have just one line, fixed height, truncated tail and fixed font size (System 17). I am interested in its width.
Any ideas?
A view should not change its own size. It should only change its intrinsicContentSize.
When you add a view to the view hierarchy, that’s when you specify whether it should observe the intrinsic content size or not (e.g. content hugging settings, compression resistance, absence of explicit width and height constraints, etc.). If you do this, the auto layout engine will do everything for you.
So, by way of example, a minimalist approach would be something that just overrides intrinsicContentSize:
#IBDesignable
class BeautifulLabel: UILabel {
#IBInspectable var marginX: CGFloat = 0 { didSet { invalidateIntrinsicContentSize() } }
#IBInspectable var marginY: CGFloat = 0 { didSet { invalidateIntrinsicContentSize() } }
#IBInspectable var cornerRadius: CGFloat = 0 { didSet { layer.cornerRadius = cornerRadius } }
override var intrinsicContentSize: CGSize {
let size = super.intrinsicContentSize
return CGSize(width: size.width + marginX * 2, height: size.height + marginY * 2)
}
}
A more complete example might be a UIView subclass, where the label is a subview, inset by the appropriate margins:
#IBDesignable
class BeautifulLabel: UIView {
#IBInspectable var marginTop: CGFloat = 0 { didSet { didUpdateInsets() } }
#IBInspectable var marginBottom: CGFloat = 0 { didSet { didUpdateInsets() } }
#IBInspectable var marginLeft: CGFloat = 0 { didSet { didUpdateInsets() } }
#IBInspectable var marginRight: CGFloat = 0 { didSet { didUpdateInsets() } }
#IBInspectable var cornerRadius: CGFloat = -1 { didSet { setNeedsLayout() } }
#IBInspectable var text: String? {
get {
label.text
}
set {
label.text = newValue
invalidateIntrinsicContentSize()
}
}
#IBInspectable var font: UIFont? {
get {
label.font
}
set {
label.font = newValue
invalidateIntrinsicContentSize()
}
}
private var topConstraint: NSLayoutConstraint!
private var leftConstraint: NSLayoutConstraint!
private var rightConstraint: NSLayoutConstraint!
private var bottomConstraint: NSLayoutConstraint!
private let label: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
override var intrinsicContentSize: CGSize {
let size = label.intrinsicContentSize
return CGSize(width: size.width + marginLeft + marginRight,
height: size.height + marginTop + marginBottom)
}
override init(frame: CGRect = .zero) {
super.init(frame: frame)
configure()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
configure()
}
override func layoutSubviews() {
super.layoutSubviews()
let maxCornerRadius = min(bounds.width, bounds.height) / 2
if cornerRadius < 0 || cornerRadius > maxCornerRadius {
layer.cornerRadius = maxCornerRadius
} else {
layer.cornerRadius = cornerRadius
}
}
}
private extension BeautifulLabel {
func configure() {
addSubview(label)
topConstraint = label.topAnchor.constraint(equalTo: topAnchor, constant: marginTop)
leftConstraint = label.leftAnchor.constraint(equalTo: leftAnchor, constant: marginLeft)
rightConstraint = rightAnchor.constraint(equalTo: label.rightAnchor, constant: marginRight)
bottomConstraint = bottomAnchor.constraint(equalTo: label.bottomAnchor, constant: marginBottom)
NSLayoutConstraint.activate([leftConstraint, rightConstraint, topConstraint, bottomConstraint])
}
func didUpdateInsets() {
topConstraint.constant = marginTop
leftConstraint.constant = marginLeft
rightConstraint.constant = marginRight
bottomConstraint.constant = marginBottom
invalidateIntrinsicContentSize()
}
}
Now in this case, I'm only exposing text and font, but you'd obviously repeat for whatever other properties you want to expose.
But let’s not get lost in the details of the above implementation. The bottom line is that a view should not attempt to adjust its own size, but rather merely its own intrinsicContentSize. And it should perform invalidateIntrinsicContentSize where necessary.

Correct remote video size on iPhoneX during video call using webrtc iOS swift

I am using webRTC for video calling. Everything is running smooth but
I am struggling with aspect ratio of Remote video on iPhoneX, XSMax. I am seeing lot of zoom in video. Can you please help me out how I can manage remote video on devices that have notch. Below is the code where I am handling remote size.
func videoView(_ videoView: RTCEAGLVideoView, didChangeVideoSize size: CGSize) {
print(size)
let defaultAspectRatio: CGSize = CGSize(width: 4, height: 3)
let aspectRatio: CGSize = size.equalTo(CGSize.zero) ? defaultAspectRatio : size
let videoRect: CGRect = self.view.bounds
let maxFloat = CGFloat.maximum(self.view.frame.width, self.view.frame.height)
let newAspectRatio = aspectRatio.width / aspectRatio.height
var frame = CGRect(x: 0, y: 0, width: self.view.frame.width, height: self.view.frame.height)
if (aspectRatio.width < aspectRatio.height) {
frame.size.width = maxFloat;
frame.size.height = frame.size.width / newAspectRatio;
} else {
frame.size.height = maxFloat;
frame.size.width = frame.size.height * newAspectRatio;
}
frame.origin.x = (self.view.frame.width - frame.size.width) / 2
frame.origin.y = (self.view.frame.height - frame.size.height) / 2
self.remoteView.frame = frame
}
According to #Eysner's answer, what is work for me, the final code (written using swift 5):
import UIKit
import WebRTC
final class WebRTCView: UIView, RTCVideoViewDelegate {
let videoView = RTCEAGLVideoView(frame: .zero)
var videoSize = CGSize.zero
override init(frame: CGRect) {
super.init(frame: frame)
videoView.delegate = self
addSubview(videoView)
}
required init?(coder: NSCoder) {
super.init(coder: coder)
videoView.delegate = self
addSubview(videoView)
}
func videoView(_ videoView: RTCVideoRenderer, didChangeVideoSize size: CGSize) {
self.videoSize = size
setNeedsLayout()
}
override func layoutSubviews() {
super.layoutSubviews()
guard videoSize.width > 0 && videoSize.height > 0 else {
videoView.frame = bounds
return
}
var videoFrame = AVMakeRect(aspectRatio: videoSize, insideRect: bounds)
let scale = videoFrame.size.aspectFitScale(in: bounds.size)
videoFrame.size.width = videoFrame.size.width * CGFloat(scale)
videoFrame.size.height = videoFrame.size.height * CGFloat(scale)
videoView.frame = videoFrame
videoView.center = CGPoint(x: bounds.midX, y: bounds.midY)
}
}
extension CGSize {
func aspectFitScale(in container: CGSize) -> CGFloat {
if height <= container.height && width > container.width {
return container.width / width
}
if height > container.height && width > container.width {
return min(container.width / width, container.height / height)
}
if height > container.height && width <= container.width {
return container.height / height
}
if height <= container.height && width <= container.width {
return min(container.width / width, container.height / height)
}
return 1.0
}
}
There is aspectFitScale function, it is simply to describe logic, you can refactor it if you want to.
I'm using this approach in the demo:
https://github.com/Maxatma/Walkie-Talkie/
You can make some class with show video -
class SomeVideoView: UIView, RTCVideoViewDelegate {
let videoView = RTCEAGLVideoView(frame: .zero)
var videoSize = CGSize.zero
With init method
override init(frame: CGRect) {
super.init(frame: frame)
videoView.delegate = self
self.addSubview(videoView)
...
Handle delegate method videoView:didChangeVideoSize like this (we don't need change frame in UI, only mark needsLayout)
func videoView(_ videoView: RTCEAGLVideoView, didChangeVideoSize size: CGSize) {
if (self.videoView == videoView) {
self.videoSize = size
}
self.setNeedsLayout()
}
And overwrite layoutSubviews method
override func layoutSubviews() {
if (self.videoSize.width > 0 && self.videoSize.height > 0) {
var videoFrame = AVMakeRect(aspectRatio: self.videoSize, insideRect: bounds)
var scale = 1.0
if (videoFrame.size.width > videoFrame.size.height) {
scale = bounds.size.height / videoFrame.size.height
} else {
scale = bounds.size.width / videoFrame.size.width
}
videoFrame.size.width = videoFrame.size.width * scale
videoFrame.size.height = videoFrame.size.height * scale
self.videoView.frame = videoFrame
self.videoView.center = CGPointMake(bounds.midX, bounds.midY)
} else {
self.videoView.frame = bounds
}
...
}
And set up SomeVideoView is full screen (this link can be help with it)
Safe Area of Xcode 9
just simple using AVMakeRect
//TODO: Default mobile
if size.width < size.height{
newSize = aspectFill(aspectRatio: size, minimumSize: UIScreen.main.bounds.size)
}else{
//Default computer
newSize = AVMakeRect(aspectRatio: size, insideRect: UIScreen.main.bounds).size
}
for sometimes user may have call from website, so i didnt make fill from it
func aspectFill(aspectRatio : CGSize, minimumSize: CGSize) -> CGSize {
let mW = minimumSize.width / aspectRatio.width;
let mH = minimumSize.height / aspectRatio.height;
var temp = minimumSize
if( mH > mW ) {
temp.width = minimumSize.height / aspectRatio.height * aspectRatio.width;
}
else if( mW > mH ) {
temp.height = minimumSize.width / aspectRatio.width * aspectRatio.height;
}
return temp;
}

iOS UILabel last lines cut off of long text with Subclassed insets

I have been attempting to get insets working with a UILabel by using this subclassed method:
#IBDesignable class TextLabelWithInsets: UILabel {
#IBInspectable var topInset: CGFloat = 0.0
#IBInspectable var leftInset: CGFloat = 0.0
#IBInspectable var bottomInset: CGFloat = 0.0
#IBInspectable var rightInset: CGFloat = 0.0
var insets: UIEdgeInsets {
get {
return UIEdgeInsetsMake(topInset, leftInset, bottomInset, rightInset)
}
set {
topInset = newValue.top
leftInset = newValue.left
bottomInset = newValue.bottom
rightInset = newValue.right
}
}
override func drawText(in rect: CGRect) {
super.drawText(in: UIEdgeInsetsInsetRect(rect, insets))
}
override var intrinsicContentSize: CGSize {
var contentSize = super.intrinsicContentSize
contentSize.width += leftInset + rightInset
contentSize.height += topInset + bottomInset
print("Content Size: " + String(describing: contentSize))
return contentSize
}
}
The problem is that this only works for smaller blocks of text. It seems as if as soon as the text gets long enough, the last line is cut off.
Any ideas how this can be fixed? Adding extra pixels to the height works but makes smaller ones look dumb. I dont know what the problem is here, the code makes sense in my head.
Here is the rest in case this helps:
func commonInit() {
oppositeSideEdgeSpacing = screen.size.width/4
textLabel = TextLabelWithInsets(frame: .zero)
textLabel.layer.cornerRadius = bubbleCornerRadius
textLabel.clipsToBounds = true
textLabel.topInset = bubbleTopInsetSpacing
textLabel.bottomInset = bubbleBottomInsetSpacing
textLabel.leftInset = bubbleLeftInsetSpacing
textLabel.rightInset = bubbleRightInsetSpacing
textLabel.numberOfLines = 0
textLabel.translatesAutoresizingMaskIntoConstraints = false
textLabel.textAlignment = .left
textLabel.backgroundColor = UIColor(hexString: "CDDCE0") // TODO: Fix this
addSubview(textLabel)
if !isMessageFromCurrentUser {
textLabel.topAnchor.constraint(equalTo: topAnchor, constant: bubbleEdgeSpacing).set(active: true)
textLabel.leftAnchor.constraint(equalTo: leftAnchor, constant: bubbleEdgeSpacing).set(active: true)
textLabel.rightAnchor.constraint(equalTo: rightAnchor, constant: -oppositeSideEdgeSpacing).set(active: true)
textLabel.bottomAnchor.constraint(equalTo: bottomAnchor).set(active: true)
}
}

Resources